Skip to content

Lifecycle Handlers

Lifecycle handlers let you run extra steps after the main DAG completes. Use the handlerOn block to trigger notifications, clean up resources, or kick off follow-up jobs without duplicating logic inside individual steps. Every handler runs with the canonical DAG_RUN_STATUS environment variable so you can branch on the final outcome inside a single script.

Supported Triggers

HandlerTriggerTypical use cases
initRuns before any workflow steps (after DAG-level preconditions pass)Setup tasks, acquire locks, validate environment
successAll steps completed successfully, or the DAG ended in partially_succeededDeliver success notifications, enqueue downstream jobs
failureThe DAG ended with a canonical failed statusPage on-call, collect diagnostics
abortA stop request interrupted the run (manual stop, queue eviction, timeout cancellation)Roll back partial work, release locks
exitAlways runs after the status-specific handler finishes (including when it fails or is skipped)File system clean-up, archival tasks

Only the handlers you define are executed. The init handler runs first (before any steps), then the main steps execute, then the status-specific handler runs (if present), and finally the exit handler runs last.

Basic Definition

yaml
# dag.yaml
handlerOn:
  init:
    command: acquire-lock.sh ${LOCK_NAME}   # runs before any steps
  success:
    command: notify.sh "${DAG_NAME} (${DAG_RUN_ID}) succeeded" # runs after a clean finish
  failure:
    command: alert.sh "${DAG_NAME} failed" "logs=${DAG_RUN_LOG_FILE}"
  abort:
    command: rollback.sh --lock ${LOCK_NAME}
  exit:
    command: rm -rf /tmp/${DAG_RUN_ID} # always runs

steps:
  - command: ./extract.sh
  - command: ./load.sh

Each handler is a normal step definition. You can use command, script, call (or the legacy run), executor, containers, timeouts, or any other step field that makes sense for a single task.

Execution Model

  • The init handler runs first, before any main steps. If it fails, the DAG is aborted and no steps execute.
  • The scheduler waits for all main steps to finish before evaluating status-specific handlers.
  • It chooses the status-specific handler based on the canonical DAG status (partially_succeeded behaves like success).
  • After the status-specific handler finishes (or if none was defined), the exit handler runs last.
  • Handlers are executed sequentially and synchronously. The DAG is still considered running until they finish.
  • If a handler exits with a non-zero status, the overall DAG run ends in failed, even if every main step succeeded.
  • Handler logs appear alongside other steps in the run history and respect the same log retention policy.
  • Each handler receives the DAG_RUN_STATUS environment variable so scripts can branch on succeeded, partially_succeeded, failed, or aborted.

Sub-DAG Handler Isolation

Important: Sub-DAGs (workflows invoked via call) do not inherit handlerOn configuration from the base DAG configuration. This design prevents unintended behavior such as:

  • Double notifications: If a parent DAG has a failure handler that sends alerts, sub-DAGs would also trigger alerts, causing duplicate notifications.
  • Unintended cleanup: Init, exit, or abort handlers meant for the root workflow would also run for every nested sub-DAG.

Each sub-DAG should define its own handlers explicitly if lifecycle handling is needed:

yaml
# parent.yaml
handlerOn:
  failure:
    command: notify.sh "Parent DAG failed"  # Only runs for parent

steps:
  - call: child-workflow

---
# child-workflow (in same file or separate file)
name: child-workflow
handlerOn:
  failure:
    command: notify.sh "Child DAG failed"  # Define explicitly if needed

steps:
  - command: process-data.sh

This isolation ensures that each workflow in a hierarchy has predictable, self-contained lifecycle behavior.

Patterns and Integrations

Send Email with the Mail Executor

yaml
handlerOn:
  failure:
    executor:
      type: mail
      config:
        to: oncall@company.com
        from: dagu@company.com
        subject: "${DAG_NAME} failed"
        message: |
          Run ID: ${DAG_RUN_ID}
          Logs: ${DAG_RUN_LOG_FILE}

Run a Follow-up DAG

yaml
handlerOn:
  success:
    call: sync-reporting
    params: |
      parent_run_id: ${DAG_RUN_ID}

Guaranteed Cleanup

yaml
handlerOn:
  exit:
    command: |
      find /tmp/${DAG_RUN_ID} -maxdepth 1 -type f -delete

For the complete schema, refer to the YAML specification. Combine handlers with the techniques from Error Handling and Data & Variables to build robust workflow lifecycles.

Released under the MIT License.