feat(workflows): expose {{ context.run_id }} template variable#2664
Open
doquanghuy wants to merge 1 commit into
Open
feat(workflows): expose {{ context.run_id }} template variable#2664doquanghuy wants to merge 1 commit into
doquanghuy wants to merge 1 commit into
Conversation
Closes github#2590. Surfaces the engine-assigned run id (the same 8-character hex string Spec Kit prints as `Run ID:` at the end of `workflow run`) as a workflow template variable so YAML authors can reference it from shell `run:`, command `input.args:`, switch `expression:`, and any other field that already evaluates `{{ ... }}` templates. ### Why The run id is the natural join key between a Spec Kit workflow run and downstream artifacts, telemetry, or per-run scratch state. Today the operator sees it in stdout but workflows themselves cannot reference it — there was no way to stamp a log line, name a scratch directory, or tag an artifact with the same id Spec Kit assigned. The three motivating use cases from the issue: 1. Telemetry / observability — stamp logs and events with the run id so external systems can join workflow runs to downstream artifacts. 2. Per-run scratch / isolation — interactive operator commands that need their own state directory under `/tmp/run-<id>/`. 3. Run-id in artifact metadata — stable join key from artifact back to the producing run. ### Implementation `StepContext.run_id` is already populated by `WorkflowEngine` in both `execute()` and `resume()`. The only gap was the template namespace builder. `_build_namespace` (in `workflows/expressions.py`) now adds a `context` key alongside the existing `inputs`, `steps`, `item`, and `fan_in` namespaces: ```python ns["context"] = {"run_id": run_id} ``` The value is always present (even outside a run) and falls back to an empty string when no run is active. Workflows referencing `{{ context.run_id }}` therefore never error — a hard requirement from the issue's acceptance criteria for dry-run, validation, and ad-hoc evaluator usage. ### Default behaviour preserved Workflows that do not reference `{{ context.run_id }}` are byte-equivalent to before this change. The `context` namespace is added unconditionally to keep template resolution branch-free, but its presence has no observable effect when nothing references it. ### Tests `TestExpressions` (unit-level) gains three tests: - `test_context_run_id_resolves` — direct lookup against a `StepContext(run_id=...)`. - `test_context_run_id_defaults_to_empty_when_unset` — graceful default outside a run context. - `test_context_run_id_string_interpolation` — mixed template (e.g. `"RUN_ID={{ context.run_id }}"`). `TestContextRunId` (end-to-end) covers the three step types the acceptance criteria called out: - `test_shell_run_resolves_run_id` — `run:` field substitution, verified via captured stdout. - `test_command_input_args_resolves_run_id` — `input.args:` resolution, captured in step output even when CLI dispatch is unavailable (the artifact-metadata use case). - `test_switch_expression_matches_on_run_id` — switch matches against the resolved value, proving the run id is a first-class value in the expression engine, not just an interpolation token. - `test_workflow_without_context_reference_unchanged` — locks the byte-equivalent default required by the issue. ### Docs `workflows/README.md` gains a "Runtime Context" subsection under "Expressions" documenting the new namespace and the three canonical use patterns (telemetry, per-run scratch, artifact metadata).
9940396 to
68634d8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Closes #2590.
Surfaces the engine-assigned run id (the same 8-character hex
string Spec Kit prints as
Run ID:at the end ofworkflow run) as a workflow template variable so YAML authorscan reference it from shell
run:, commandinput.args:,switch
expression:, and any other field that already evaluates{{ ... }}templates.This is shape A from the issue (
{{ context.run_id }}) —the most discoverable option and consistent with the existing
inputs.*/steps.X.output.*naming.Why
The run id is the natural join key between a Spec Kit workflow
run and downstream artifacts, telemetry, or per-run scratch
state. Today the operator sees it in stdout but workflows
themselves cannot reference it — there was no way to stamp a
log line, name a scratch directory, or tag an artifact with the
same id Spec Kit assigned.
The three use cases from the issue:
the run id so external systems can join workflow runs to
downstream artifacts.
commands that need their own state directory under
/tmp/run-<id>/.artifact back to the producing run.
Canonical usage
Implementation
StepContext.run_idis already populated byWorkflowEnginein both
execute()andresume(). The only gap was thetemplate namespace builder.
_build_namespace(inworkflows/expressions.py) now adds acontextkey alongside the existinginputs,steps,item,and
fan_innamespaces:The value is always present (even outside a run) and falls back
to an empty string when no run is active. Workflows referencing
{{ context.run_id }}therefore never error — a hardrequirement from the issue's acceptance criteria for dry-run,
validation, and ad-hoc evaluator usage.
Default behaviour preserved
Workflows that do not reference
{{ context.run_id }}arebyte-equivalent to before this change. The
contextnamespaceis added unconditionally to keep template resolution
branch-free, but its presence has no observable effect when
nothing references it.
Testing
uv run specify --helpuv sync && uv run pytest→ 2967 passed, 35 skipped (was 2960 before; +7 new
tests added in this PR).
run: 'echo "RUN_ID={{ context.run_id }}"'and confirmedthe captured stdout matches the
Run ID:line Spec Kitprints at the end of
workflow run. Re-ran without thetemplate reference and the workflow behaved identically
to pre-PR.
New test coverage
TestExpressions(unit-level):test_context_run_id_resolvesStepContext(run_id=...).test_context_run_id_defaults_to_empty_when_unsettest_context_run_id_string_interpolation"RUN_ID={{ context.run_id }}".TestContextRunId(end-to-end), covering the three step types the issue's acceptance criteria called out:test_shell_run_resolves_run_idrun:field substitution, verified via captured stdout.test_command_input_args_resolves_run_idinput.args:resolution, captured in step output even when CLI dispatch is unavailable (the artifact-metadata use case).test_switch_expression_matches_on_run_idtest_workflow_without_context_reference_unchangedAI Disclosure
Used Claude Opus to draft the namespace change, the test suite,
the docs section, and this PR body. The shape
(
{{ context.run_id }}with empty-string fallback) wasproposed in the issue body; this PR implements that proposal.
Code, tests, and design decisions were human-reviewed before
submission.