Approval
Add approval to any step to pause execution after the step completes and wait for human review.
Usage
steps:
- id: deploy_staging
command: ./deploy.sh staging
approval:
prompt: "Verify staging deployment before production"
- id: deploy_prod
depends: [deploy_staging]
command: ./deploy.sh productionThe deploy_staging step runs ./deploy.sh staging, then enters Waiting status. The deploy_prod step remains Not Started until the approval is resolved.
Configuration
| Field | Type | Description |
|---|---|---|
prompt | string | Message displayed to the approver |
input | string[] | Parameter names to collect from the approver |
required | string[] | Parameters that must be provided (subset of input) |
All fields are optional. A bare approval: {} is valid — no prompt, no inputs, just a pause.
Validation: every entry in required must also appear in input. The build fails otherwise.
How It Works
- The step executes normally (command runs, produces stdout/stderr)
- After successful execution, the step enters
Waitingstatus - The DAG status becomes
Waiting - Dependent steps remain
Not Started - A human reviews the step output and chooses one of:
- Approve — step succeeds, dependents execute
- Push back — step resets to
Not Startedand re-executes (see Push-back) - Reject — step enters
Rejectedstatus, DAG becomesRejected, dependents are aborted
Examples
Collecting Inputs
Approved inputs become environment variables in subsequent steps:
type: graph
steps:
- id: generate_plan
command: ./generate-migration-plan.sh
approval:
prompt: "Review migration plan"
input: [APPROVED_BY, MAINTENANCE_WINDOW]
required: [APPROVED_BY]
- id: execute_migration
depends: [generate_plan]
command: ./migrate.sh --approver "${APPROVED_BY}" --window "${MAINTENANCE_WINDOW}"APPROVED_BY must be provided (it's in required). MAINTENANCE_WINDOW is optional. Both are injected as environment variables into execute_migration after approval.
Gating a Sub-DAG
Use call with approval to gate a multi-step workflow behind a single approval point. The sub-DAG runs to completion first, then the step waits for review:
type: graph
steps:
- id: run_integration_tests
call: integration-test-suite
approval:
prompt: "Review test results before deploying"
- id: deploy
depends: [run_integration_tests]
command: ./deploy.sh productionThe integration-test-suite DAG (which may contain many steps internally) executes fully. Once finished, run_integration_tests enters Waiting. The approver reviews the sub-DAG's results before deploy proceeds.
This pattern is useful when you want human review over a complex operation that involves multiple internal steps — tests, builds, migrations — without adding approval to each individual sub-step.
Approval Before a Sub-DAG
The reverse pattern: approve first, then trigger multi-step execution. Place approval on the step before a call:
type: graph
steps:
- id: review_config
command: ./validate-deploy-config.sh production
approval:
prompt: "Config validated. Approve production deployment?"
input: [DEPLOY_VERSION]
required: [DEPLOY_VERSION]
- id: deploy_pipeline
depends: [review_config]
call: production-deploy
params: "deploy_version=${DEPLOY_VERSION}"validate-deploy-config.sh runs and shows the configuration diff. The approver reviews it, provides DEPLOY_VERSION, and approves. Then production-deploy (a full deployment pipeline with its own steps) executes with the approved version.
Push-back
Push-back resets a waiting step to Not Started and re-executes it. This is useful when a step's output needs revision — the approver provides feedback, and the step re-runs with that feedback available as environment variables.
Push-back is only available on steps with the approval field.
How Push-back Works
- A step executes and enters
Waiting - The approver reviews the output and pushes back with input parameters
- The step resets to
Not Started - All transitive downstream dependents also reset to
Not Started - The step re-executes with push-back inputs injected as environment variables
- The
approvalIterationcounter increments (starts at 0, becomes 1 after first push-back) - The step enters
Waitingagain — the approver can approve, push back again, or reject
Example
A step queries metrics and outputs a summary. The approver can push back with different parameters until the output looks right:
steps:
- id: query_metrics
script: |
SINCE="${SINCE:-7d}"
GROUPING="${GROUPING:-daily}"
echo "Querying error rates (since=$SINCE, grouping=$GROUPING)"
curl -s "https://metrics.internal/api/errors?since=$SINCE&group=$GROUPING" \
| jq '.[] | "\(.date): \(.count) errors (\(.rate)%)"'
approval:
prompt: "Review error rate summary. Push back to adjust query parameters."
input: [SINCE, GROUPING]
- id: send_report
depends: [query_metrics]
command: ./send-to-slack.shFirst run: SINCE and GROUPING are unset, so the script defaults to 7d and daily. The approver reviews the output and pushes back with SINCE=30d and GROUPING=weekly. The step re-runs with those values, producing a different summary. The approver can push back again or approve.
Example: Iterating on LLM Output
Use claude -p in a command step to generate content, then push back with feedback to regenerate:
steps:
- id: draft_changelog
script: |
PROMPT="Generate a changelog from these git commits for a public release blog post."
if [ -n "$FEEDBACK" ]; then
PROMPT="$PROMPT Incorporate this feedback: $FEEDBACK"
fi
git log --oneline v1.2.0..HEAD | claude -p "$PROMPT"
approval:
prompt: "Review the draft changelog. Push back with feedback to revise."
input: [FEEDBACK]
- id: publish
depends: [draft_changelog]
command: ./publish-changelog.shFirst run: Claude generates a changelog from the git log. The reviewer reads the output in the Approval tab and pushes back with FEEDBACK="Make it more concise and group by feature area". The step re-runs, this time passing the feedback into the prompt. This loop continues until the reviewer approves.
REST API
curl -X POST "http://localhost:8080/api/v1/dag-runs/{name}/{dagRunId}/steps/{stepName}/push-back" \
-H "Content-Type: application/json" \
-d '{
"inputs": {
"SINCE": "30d",
"GROUPING": "weekly"
}
}'Response:
{
"dagRunId": "...",
"stepName": "query_metrics",
"approvalIteration": 1,
"resumed": true
}| Field | Type | Description |
|---|---|---|
approvalIteration | integer | How many times this step has been pushed back |
resumed | boolean | Whether the DAG run was re-enqueued for execution |
For sub-DAG runs, use the sub-DAG endpoint:
POST /api/v1/dag-runs/{name}/{dagRunId}/sub-dag-runs/{subDAGRunId}/steps/{stepName}/push-backApproval and Rejection
Web UI
When steps enter Waiting status, an Approval tab appears in the DAG run view. The tab shows:
- Each waiting step with its name and prompt
- The step's stdout output inline
- Approve and Retry (push-back) buttons per step
- The current approval iteration count (if pushed back)
To reject all waiting steps at once, use the Reject button in the DAG run action bar (replaces the Stop button when the DAG is in Waiting status). An optional rejection reason can be provided.
REST API
Approve a Step
curl -X POST "http://localhost:8080/api/v1/dag-runs/{name}/{dagRunId}/steps/{stepName}/approve" \
-H "Content-Type: application/json" \
-d '{
"inputs": {
"APPROVED_BY": "john@example.com"
}
}'Reject a Step
curl -X POST "http://localhost:8080/api/v1/dag-runs/{name}/{dagRunId}/steps/{stepName}/reject" \
-H "Content-Type: application/json" \
-d '{
"reason": "Deployment blocked due to pending security review"
}'The reason field is optional.
For sub-DAG runs, use the sub-DAG variants:
POST /api/v1/dag-runs/{name}/{dagRunId}/sub-dag-runs/{subDAGRunId}/steps/{stepName}/approve
POST /api/v1/dag-runs/{name}/{dagRunId}/sub-dag-runs/{subDAGRunId}/steps/{stepName}/rejectEmail Notifications
Configure email notifications when a workflow enters wait status:
mail_on:
wait: true
wait_mail:
from: dagu@example.com
to:
- approvers@example.com
prefix: "[APPROVAL REQUIRED]"See Email Notifications for details.
Wait Handler
Execute custom logic when the workflow enters wait status:
handler_on:
wait:
command: |
echo "Waiting steps: ${DAG_WAITING_STEPS}"
curl -X POST https://slack.com/webhook \
-d '{"text": "Approval required for ${DAG_NAME}"}'
steps:
- id: deploy
command: ./deploy.sh
approval:
prompt: "Approve deployment"The DAG_WAITING_STEPS environment variable contains a comma-separated list of waiting step names.
See Lifecycle Handlers for details.
Rejection Behavior
When a step is rejected:
- The step status changes to
Rejected - The overall DAG status becomes
Rejected - All dependent steps are marked as
Abortedand will not execute - The
onFailurehandler is executed (if configured)
The following information is recorded:
| Field | Description |
|---|---|
rejectedAt | Timestamp of the rejection |
rejectedBy | Username of the person who rejected (if authenticated) |
rejectionReason | Optional reason provided during rejection |
Limitations
- Steps with
approvalcannot useworker_selector(distributed execution) because approval state is stored locally
See Also
- Lifecycle Handlers — Execute handlers on wait status
- Email Notifications — Configure wait status emails
- Step Types Reference — All available step types
