# Spec: Built-In Run Context ## Status Implemented. ## Scope This spec defines Dagu-managed context values for one DAG run and the current step or handler inside that run. It covers: - structured built-in context references such as `${context.run.id}` - environment-variable projections such as `DAG_RUN_ID` - availability rules for run, attempt, step, handler, trigger, path, profile, and push-back context values This spec does not define: - user-authored `env` declarations - parameter declaration, validation, individual `${params.name}` references, or root `${params}` payload references - step output declaration or `${steps.step_id.outputs.name}` references - secret provider lookup or secret masking - lifecycle handler execution order - scheduler, queue, coordinator, worker, API, or UI behavior - integration-specific convenience variables such as provider-owned GitHub variables Environment precedence and protected-name behavior are defined by [Spec 006: Value Resolution Env](006-value-resolution-env.md). Common value resolution syntax, escaping, field support, and passive notices are defined by [Spec 003: Value Resolution and Field Evaluation](003-value-resolution.md). ## Goal Workflow authors can read stable DAG-run metadata without depending on ambient host environment, storage layout, UI state, or scheduler internals. Shell-oriented steps can continue to use small environment variables. Object-valued payloads stay on their owning specs or compatibility environment variables instead of becoming `context.*` string references. ## Motivation Built-in run metadata is a public workflow-language surface. Once a variable is observable, workflow authors may depend on its name, value, timing, and overwrite behavior. Dagu needs one contract that separates: - stable run identity, such as the DAG name and DAG-run ID - current execution context, such as the current step name and stream files - optional trigger context, such as scheduled time or webhook payload - filesystem locations, such as logs, work directory, and artifacts directory - compatibility environment variables used by existing shell workflows The context must be deterministic for the current run. It must not read unrecorded host state during a later step, retry, handler, or distributed execution hop. ## Related Specs - Value resolution: [Spec 003: Value Resolution and Field Evaluation](003-value-resolution.md) - Environment values: [Spec 006: Value Resolution Env](006-value-resolution-env.md) - Step identity: [Spec 009: Step Reference](009-step-reference.md) - Step outputs: [Spec 012: Step Outputs](012-step-outputs.md) - Step run: [Spec 013: Step Run](013-step-run.md) ## Behavior ### Context Values A context value is Dagu-managed runtime data about the current DAG run or the current step or handler. Rules: - Context values are read-only from the workflow author's perspective. - User-authored `env`, dotenv, runtime-profile variables, step outputs, and secrets must not override protected context values in the final environment scope where Spec 006 says the name is protected. - Context values must not contain Dagu-managed secret values. - Context values must not expose the raw host process environment. - Context values must be stable for the lifecycle scope that owns them. - A value that is unavailable in a lifecycle scope is missing, not an empty string, unless a field definition below explicitly says the value is empty. - Missing structured context references are preserved according to Spec 003 missing-reference behavior. ### Structured Reference Syntax This spec adds canonical Dagu-owned reference forms under the `context` namespace: ```text ${context.dag.name} ${context.run.id} ${context.run.status} ${context.run.scheduled_at} ${context.run.root_name} ${context.run.root_id} ${context.attempt.id} ${context.attempt.started_at} ${context.step.id} ${context.step.name} ${context.step.repeat_index} ${context.trigger.type} ${context.trigger.actor} ${context.paths.log_file} ${context.paths.work_dir} ${context.paths.artifacts_dir} ${context.paths.step_stdout_file} ${context.paths.step_stderr_file} ${context.paths.step_output_file} ${context.profile.name} ${context.profile.resolved_at} ${context.pushback.iteration} ${context.pushback.previous_stdout_file} ``` Rules: - These references are Dagu-owned references only in fields that support value resolution under Spec 003. - `context` is the canonical root namespace for Dagu-managed runtime context. Workflow-authored data remains under its owning namespace, such as `params`, `env`, or `steps`. - All structured context references in this spec resolve to scalar string values. - Object-valued context, such as webhook payload, webhook headers, profile entries, and push-back history, is outside this spec's structured string references. - Unknown fields under reserved context namespaces are preserved at runtime. Explicit inspection surfaces must report an inspection-only passive notice with reason `unknown_context_field`. - Unsupported reference-looking text outside reserved context namespaces is preserved silently as ordinary string content under Spec 003. - Escaped context-looking text follows Spec 003 escape behavior. - `context`, `context.dag`, `context.run`, `context.attempt`, `context.step`, `context.trigger`, `context.paths`, `context.profile`, and `context.pushback` are reserved context namespaces. - The short top-level namespaces `dag`, `run`, `attempt`, `step`, `trigger`, `paths`, `profile`, and `pushback` are frozen compatibility aliases. New context fields must not be added to those alias namespaces. - Only the exact frozen alias forms listed in the compatibility table are Dagu-owned references. Other text under those short roots, such as `${step.xxx.foo}`, is unsupported ordinary string content under Spec 003. - A workflow parameter named `context`, `run`, `step`, or another context namespace remains addressable through `${params.context}`, `${params.run}`, or `${params.step}`. It must not shadow a context namespace. ### Namespace Design The `context` root separates Dagu-managed runtime metadata from workflow-authored data. This keeps common top-level names available for future workflow-language specs and makes the trust boundary explicit: - `params`, `env`, and `steps` are workflow-authored or workflow-derived data. - `context` is Dagu-managed runtime data. - Environment variables are a compatibility projection of selected context values, not the source of truth for structured context references. ### Namespace Fields `context.dag` fields: | Field | Availability | Meaning | | --- | --- | --- | | `context.dag.name` | All run, step, and handler scopes | Name of the DAG definition being executed. | `context.run` fields: | Field | Availability | Meaning | | --- | --- | --- | | `context.run.id` | All run, step, and handler scopes | Unique identifier for the current DAG run. | | `context.run.status` | Status-aware scopes only | Canonical lowercase run status. | | `context.run.scheduled_at` | Scheduled, catchup, and one-off scheduled runs only | UTC RFC3339 timestamp for the logical schedule time that caused the run. | | `context.run.root_name` | Sub-DAG runs only | DAG name of the root DAG run. | | `context.run.root_id` | Sub-DAG runs only | DAG-run ID of the root DAG run. | Rules: - `context.run.id` is the durable identity for a DAG run. - `context.run.scheduled_at` is not a unique run identifier. Scheduled, catchup, and retry behavior may create multiple runs associated with one logical schedule time. Workflow authors must use `context.run.id` when they need identity. - `context.run.status` is available in lifecycle handlers and other explicitly status-aware surfaces. It is unavailable in normal step execution unless an owning spec explicitly makes that surface status-aware. - Valid `context.run.status` values are `not_started`, `queued`, `running`, `succeeded`, `partially_succeeded`, `failed`, `aborted`, `waiting`, `rejected`, and `unknown`. `context.attempt` fields: | Field | Availability | Meaning | | --- | --- | --- | | `context.attempt.id` | Attempt-aware run, step, and handler scopes | Identifier for the current DAG-run attempt. | | `context.attempt.started_at` | After run-attempt start is recorded | UTC RFC3339 timestamp for the start of this DAG-run attempt. | Rules: - `context.attempt.id` identifies the current attempt for one DAG run. - `context.attempt.id` is not the same as `context.run.id`. - Step retry attempts are outside this namespace unless a step-retry-owning spec adds a separate field. `context.step` fields: | Field | Availability | Meaning | | --- | --- | --- | | `context.step.id` | Current executable step only, when that step has an id | Stable value-reference alias from Spec 009. | | `context.step.name` | Current step or handler only | Runtime step identity for the current step or handler. | | `context.step.repeat_index` | Current step with `repeat_policy` only | Zero-based index of the current execution as a decimal string. | Rules: - `context.step.name` is the runtime step identity defined by Spec 009. - `context.step.id` is missing when the current executable step has no authored `id`. - Handler scopes have `context.step.name` but do not have `context.step.id` unless a handler-owning spec defines an id for handlers. - `context.step.repeat_index` is `0` for the first execution of a repeating step and increases by one for each repeat. A step retry keeps the index of the execution it retries. - Dagu resolves the step again for every repeat, so `run`, `with`, `stdin`, and step `env` see the index of the current execution. `repeat_policy.condition` sees the index of the execution that just finished. - Fields resolved once before the first execution keep index `0`: `stdout`, `stderr`, `preconditions`, and the `repeat_policy` and `retry_policy` settings. - `context.step.repeat_index` is missing for steps without `repeat_policy` and for handlers. `context.trigger` fields: | Field | Availability | Meaning | | --- | --- | --- | | `context.trigger.type` | All run, step, and handler scopes when known | Canonical trigger type. | | `context.trigger.actor` | Runs started by an authenticated or attributable actor | Stable actor identifier safe to expose to the workflow. | Rules: - Valid `context.trigger.type` values are `unknown`, `manual`, `scheduler`, `webhook`, `subdag`, `retry`, `catchup`, and `restart`. `restart` is the trigger type of the runs started because of a DAG's `restart_policy` ([Spec 080](080-restart-policy.md)). - `context.trigger.actor` must not contain credentials, bearer tokens, session IDs, or other authenticators. - Webhook payload and headers are object-valued trigger context and are exposed through compatibility environment variables. `context.paths` fields: | Field | Availability | Meaning | | --- | --- | --- | | `context.paths.log_file` | All run, step, and handler scopes | Absolute path to the aggregated DAG-run log file. | | `context.paths.work_dir` | When a per-run work directory is available | Absolute path to the per-run work directory. | | `context.paths.artifacts_dir` | When artifact storage is active | Absolute path to the per-run artifacts directory or staging directory. | | `context.paths.step_stdout_file` | Current executable step after stream files are assigned | Absolute path to the current step stdout file. | | `context.paths.step_stderr_file` | Current executable step after stream files are assigned | Absolute path to the current step stderr file. | | `context.paths.step_output_file` | Current step attempt after output publication is prepared | Absolute path to the current step output file used by Spec 012. | Rules: - Path values are filesystem paths as seen by the current step or handler. - A path value may point to worker-local staging storage in an execution mode where final storage is remote or coordinator-owned. - Workflow authors must not infer storage retention, public URL shape, or UI availability from a path value. - A reference to `context.paths.artifacts_dir` in the DAG definition enables artifact storage unless artifact storage is explicitly disabled. `context.profile` fields: | Field | Availability | Meaning | | --- | --- | --- | | `context.profile.name` | Runs started with a selected runtime profile | Runtime profile selected for the run. | | `context.profile.resolved_at` | Runs started with a selected runtime profile | UTC RFC3339 timestamp at which the selected profile was resolved. | Rules: - Profile values are metadata only. - Profile variables and profile secrets are owned by the runtime-profile and secret specs, not by this spec. `context.pushback` fields: | Field | Availability | Meaning | | --- | --- | --- | | `context.pushback.iteration` | Steps re-executed after an approval or human-task push-back | Current push-back iteration as a decimal string. | | `context.pushback.previous_stdout_file` | Rewound steps that had previous stdout | Absolute path to the previous stdout log for the current step. | Rules: - Push-back inputs and history are object-valued context and are exposed through compatibility environment variables. - Push-back context is missing on the first execution before any push-back occurs. - `DAG_PUSHBACK` stays within 30 KiB (30720 bytes). When the full history does not fit, the payload keeps the most recent history entries that fit; the run status keeps the full history. - The inputs of one push-back, encoded as one JSON object, are limited to 16 KiB (16384 bytes). A larger push-back is rejected before it changes the run. - A step that declares `approval.input` receives only those input names from an approval push-back. Human-task push-back feedback holds only declared feedback properties and reaches every rewound step unchanged. ### Environment Projection Environment projection is the flat process-environment view of selected context values. Rules: - Environment projection is for compatibility with shell scripts and tools that read process environment variables. - Structured references are the canonical surface for new scalar context fields. - Environment projection must not expose object-valued context except as JSON strings for compatibility variables already defined by this spec. - Environment projection names are reserved according to Spec 006. - A context value unavailable in the current scope must not be projected unless the variable definition below explicitly allows an empty value. Run-level projection: | Environment variable | Source | Availability | | --- | --- | --- | | `DAG_NAME` | `context.dag.name` | All steps and handlers. | | `DAG_RUN_ID` | `context.run.id` | All steps and handlers. | | `DAG_RUN_LOG_FILE` | `context.paths.log_file` | All steps and handlers. | | `DAG_RUN_WORK_DIR` | `context.paths.work_dir` | When a per-run work directory is available. | | `DAG_RUN_ARTIFACTS_DIR` | `context.paths.artifacts_dir` | When artifact storage is active. | | `DAG_PARAMS_JSON` | Resolved parameter payload JSON; same payload as `${params}` from Spec 005 | When resolved parameters exist. | Step and handler projection: | Environment variable | Source | Availability | | --- | --- | --- | | `PWD` | Current process working directory | Current step only. | | `DAG_RUN_STEP_NAME` | `context.step.name` | Current step or handler only. | | `DAG_RUN_STEP_REPEAT_INDEX` | `context.step.repeat_index` | Current step with `repeat_policy` only. | | `DAG_RUN_STEP_STDOUT_FILE` | `context.paths.step_stdout_file` | Current executable step after stream files are assigned. | | `DAG_RUN_STEP_STDERR_FILE` | `context.paths.step_stderr_file` | Current executable step after stream files are assigned. | | `DAGU_OUTPUT_FILE` | `context.paths.step_output_file` | Current step attempt after output publication is prepared. | Trace context projection: | Environment variable | Source | Availability | | --- | --- | --- | | `TRACEPARENT` | W3C `traceparent` of the current step span | Current executable step when the step span is valid. | | `TRACESTATE` | W3C `tracestate` of the current step span | Current executable step when the step span is valid and has a trace state. | Status and wait-handler projection: | Environment variable | Source | Availability | | --- | --- | --- | | `DAG_RUN_STATUS` | `context.run.status` | Lifecycle handlers and status-aware surfaces only. | | `DAG_WAITING_STEPS` | Waiting step names | Wait handler only. | Push-back projection: | Environment variable | Source | Availability | | --- | --- | --- | | `DAG_PUSHBACK` | Push-back metadata JSON | Steps re-executed after an approval or human-task push-back only. | | `DAG_PUSHBACK_ITERATION` | `context.pushback.iteration` | Steps re-executed after an approval or human-task push-back only. | | `DAG_PUSHBACK_PREVIOUS_STDOUT_FILE` | `context.pushback.previous_stdout_file` | Rewound steps that had previous stdout. | Webhook projection: | Environment variable | Source | Availability | | --- | --- | --- | | `WEBHOOK_PAYLOAD` | Trigger payload JSON | Webhook-triggered runs only. | | `WEBHOOK_HEADERS` | Allowlisted trigger headers JSON | Webhook-triggered runs only. | Rules: - `WEBHOOK_PAYLOAD` and `WEBHOOK_HEADERS` are compatibility runtime-param environment values. - They are not protected Dagu-managed environment names under Spec 006. - They follow Spec 006 runtime-param precedence after parameter resolution. - They are untrusted trigger input. They must not carry credentials, authorization headers, cookies, or values used by Dagu itself for authorization decisions. - When a DAG explicitly declares either name as a runtime parameter, that name behaves as a normal user parameter for that DAG. - When a webhook trigger supplies either name and the DAG does not declare it as a runtime parameter, the trigger-provided value is an internal runtime-param value. Reserved specialized run-control names: - `DAGU_EXTERNAL_STEP_RETRY` - `DAGU_QUEUE_DISPATCH_RETRY` These names are reserved for owning run-control surfaces. They are not normal workflow context values unless an owning spec explicitly adds them. ### Trace Context Projection When OpenTelemetry tracing is enabled for a DAG, Dagu records a span for each executable step. Trace context projection lets a process that the step starts create child spans of that step span. Rules: - The step span is valid only when tracing is enabled for the DAG and the run has a valid DAG span. Without a valid step span, Dagu projects neither name. - `TRACEPARENT` and `TRACESTATE` use the W3C Trace Context format. The names are the upper-case propagator keys, as in the OpenTelemetry convention for environment variables as context carriers. - `TRACEPARENT` names the trace of the DAG run and the span ID of the current step span. A child span of the step span uses this span ID as its parent. - `TRACESTATE` is projected only when the step span has a trace state. When the step span has no trace state and the root run environment scope has a `TRACESTATE`, Dagu replaces it with an empty value, because it belongs to another span. - Lifecycle handlers have no step span, so Dagu projects neither name for them. - The names extend the OpenTelemetry carrier convention, not the `DAG_` or `DAGU_` families. Tools read these exact names, so the `DAGU_` prefix rule for new projection variables does not apply. - Precedence follows Spec 006: the names replace root-scope values of the same name, and a step-local source that sets either name shadows both names. A root-scope value of the name that the step does not set becomes empty. A step can set `TRACEPARENT` to an empty string to give its process no trace context. ### Availability Availability rules: - DAG and run identity are available after the DAG run is created. - Attempt identity is available only in scopes that know the current DAG-run attempt. - Step context is available only while evaluating or executing the current step or handler. - Repeat context is available only for steps with `repeat_policy`. - Step stream paths are available only after stdout and stderr files are assigned for the current executable step. - Step output file path is available only after step output publication is prepared for the current step attempt. - Artifact directory is available when artifact storage is active. A DAG definition reference to `context.paths.artifacts_dir` activates artifact storage unless artifact storage is explicitly disabled. - Profile context is available only when a runtime profile was selected. - Webhook context is available only for webhook-triggered runs. - Push-back context is available only for step executions caused by an approval or human-task push-back cycle. Validation rules: - `dagu validate` must not fail only because a structured context reference is unavailable during validation. - Explicit inspection surfaces must report passive notices for unresolved supported structured context references. - A supported structured context reference carries a value only during a run, so its notice is runtime-only. `dagu validate` must keep it out of the default output and print it under `--show-unresolved`. - A reference to an unsupported field under `context.` remains a defect and is reported by default, which is what separates a typo such as `${context.paths.artifact_dir}` from the supported `${context.paths.artifacts_dir}`. - Explicit inspection surfaces must report passive notices with reason `unknown_context_field` for unknown fields under reserved context namespaces. - Normal run execution must not emit passive value-reference notices as run logs, workflow events, status data, history data, artifacts, or DAG-run detail data. ### Security Rules: - Context values must be treated as ordinary workflow input by the step that consumes them. - Trigger payloads and trigger headers are untrusted external input. - Compatibility JSON-string environment values must not include Dagu-managed secret values or authenticators. - Compatibility JSON-string environment values must not include unallowlisted webhook headers. - Environment projection must not introduce new secret exposure paths beyond variables explicitly owned by secret or profile specs. - A context reference that resolves to a string must not be shell-escaped by Dagu. Shell-backed fields follow the quoting rules in Spec 013. - Projected environment variables are not authenticators, trusted audit facts, or Dagu-internal authority. Dagu internals must use structured runtime context, not process environment values, for authorization, identity, status, output-file routing, or audit decisions. ### Compatibility Rules: - Existing environment variable names listed in this spec remain supported. - `DAG_PARAMS_JSON` is the parameter payload environment variable. - New structured context fields must be additive. - New structured context fields must be added under `context.*`. - Short top-level aliases are frozen. They remain supported for the exact fields listed below, but they are not canonical and must not receive new fields. - New environment projection variables should be added only when a value is broadly useful to shell scripts and cannot reasonably be represented as a structured reference or an existing compatibility variable. - New environment projection variables should prefer the `DAGU_` prefix unless an owning spec explicitly extends an existing compatibility family such as `DAG_` or `WEBHOOK_`. - `DAG_RUN_STEP_REPEAT_INDEX` extends the `DAG_RUN_STEP_` family that already carries the current step's name and stream files. Frozen structured-reference aliases: | Compatibility alias | Canonical reference | | --- | --- | | `dag.name` | `context.dag.name` | | `run.id` | `context.run.id` | | `run.status` | `context.run.status` | | `run.started_at` | `context.attempt.started_at` | | `run.scheduled_at` | `context.run.scheduled_at` | | `run.root_name` | `context.run.root_name` | | `run.root_id` | `context.run.root_id` | | `attempt.id` | `context.attempt.id` | | `step.id` | `context.step.id` | | `step.name` | `context.step.name` | | `trigger.type` | `context.trigger.type` | | `trigger.actor` | `context.trigger.actor` | | `paths.log_file` | `context.paths.log_file` | | `paths.work_dir` | `context.paths.work_dir` | | `paths.artifacts_dir` | `context.paths.artifacts_dir` | | `paths.step_stdout_file` | `context.paths.step_stdout_file` | | `paths.step_stderr_file` | `context.paths.step_stderr_file` | | `paths.step_output_file` | `context.paths.step_output_file` | | `profile.name` | `context.profile.name` | | `profile.resolved_at` | `context.profile.resolved_at` | | `pushback.iteration` | `context.pushback.iteration` | | `pushback.previous_stdout_file` | `context.pushback.previous_stdout_file` | ## Errors Validation must fail when: - a workflow attempts to define a secret whose name collides with a secret-reserved context environment variable under Spec 006. - a field shape owned by another spec is invalid before context references are evaluated. Runtime must fail before starting the current step or handler when: - a projected context environment variable cannot be represented as a process environment entry for the target platform. - a context value required by the current executor surface cannot be made available after that surface declares it required. Runtime must preserve the original reference text when: - a supported structured context reference is unavailable in the current lifecycle scope. - a supported structured context reference is unavailable because an optional feature, such as artifacts, docs, profile, webhook, or push-back, is not active for this run. - a reserved context reference names an unknown field. Inspection surfaces report `unknown_context_field`, but normal runtime surfaces stay silent. ## Examples Use stable run identity in a notification: ```yaml steps: - id: notify run: notify.sh '${context.dag.name}' '${context.run.id}' ``` Use a per-run work directory when it is available: ```yaml steps: - id: write_scratch run: | mkdir -p '${context.paths.work_dir}' printf '%s\n' '${context.run.id}' > '${context.paths.work_dir}/run-id.txt' ``` Use handler-only run status: ```yaml handler_on: exit: run: notify.sh '${context.dag.name}' '${context.run.id}' '${context.run.status}' ``` Unsupported normal-step status reference is preserved until a status-aware surface owns it: ```yaml steps: - id: print_status run: printf '%s\n' '${context.run.status}' ``` In a normal step, `context.run.status` is unavailable. Dagu preserves the reference text and hands it to the selected shell according to Spec 013.