Lifecycle Hooks

Hooks run after DAG or step completion. They can be R expressions or shell commands.

DAG-level hooks

on_success:
  r_expr: 'slackr::slackr_msg("Pipeline succeeded")'
on_failure:
  command: echo "FAILED" >> /var/log/alerts.log
on_exit:
  r_expr: 'logger::log_info("Done")'
Hook Runs when
on_success DAG completes with all steps passing
on_failure Any step fails (after retries exhausted)
on_exit Always, regardless of outcome

Step-level hooks

steps:
  - id: model
    script: fit.R
    on_success:
      r_expr: 'saveRDS(Sys.time(), "last_success.rds")'
    on_failure:
      r_expr: 'saveRDS(last.warning, "debug.rds")'

Step-level hooks have on_success and on_failure only (no on_exit).

Hook types

Each hook is an R expression, a shell command, or a reference to a named notification channel — exactly one:

on_failure:
  r_expr: 'slackr::slackr_msg("Step failed")'
  # OR
  command: echo "Step failed" | mail -s "Alert" admin@example.com
  # OR
  notify: team-slack

Notification hooks

The notify: form dispatches through a named channel configured globally in ~/.config/daggle/config.yaml. Channels are sent directly from Go — no R required — so they work even when R is unavailable or the DAG has no R steps.

# ~/.config/daggle/config.yaml
notifications:
  team-slack:
    type: slack
    webhook_url: https://hooks.slack.com/services/...
  ops-email:
    type: smtp
    smtp_host: mail.example.com
    smtp_port: 587
    smtp_from: daggle@example.com
    smtp_to: [ops@example.com]
    smtp_user: daggle
    smtp_password: ${SMTP_PASSWORD}

Reference the channel by name from a hook:

on_failure:
  notify: team-slack
  message: "ETL failed — see ${DAGGLE_RUN_DIR}"   # optional; default uses DAG name + status

Supported channel types: slack, clickup, http (generic webhook), smtp. Unknown channel names are rejected at DAG parse time, so typos fail fast rather than silently dropping notifications.

Available environment

Hooks receive all run metadata and accumulated step outputs as environment variables:

  • DAGGLE_RUN_ID – current run ID
  • DAGGLE_DAG_NAME – DAG name
  • DAGGLE_RUN_DIR – run directory path
  • DAGGLE_OUTPUT_<STEP>_<KEY> – all outputs from completed steps

Common patterns

Slack notification:

on_failure:
  r_expr: |
    slackr::slackr_msg(
      sprintf("DAG %s failed (run %s)",
              Sys.getenv("DAGGLE_DAG_NAME"),
              Sys.getenv("DAGGLE_RUN_ID"))
    )

Email alert:

on_failure:
  command: echo "Pipeline failed at $(date)" | mail -s "daggle alert" team@example.com

Logging:

on_exit:
  r_expr: 'logger::log_info("DAG finished")'