Bug Description
workflows/engine.py namespaces nested step IDs per iteration in
while / do-while loops:
# engine.py around line 678
ns_copy["id"] = f"{step_id}:{ns_copy['id']}:{_loop_iter + 1}"
But the loop's condition (resolved via
expressions.evaluate_condition →
_resolve_dot_path → _build_namespace) reads step output by the
unprefixed step ID. So a condition like
{{ steps.my-gate.output.choice }} != 'approve' reads the
iteration-0 value on every subsequent iteration, because
steps.my-gate in the namespace points at the original
un-namespaced step output and is never updated to track
steps.my-gate:my-loop:1, :2, etc.
Net: gate-driven loop termination based on a gate's verdict
never fires. The loop runs to max_iterations.
Steps to Reproduce
schema_version: "1.0"
workflow:
id: while-loop-bug-repro
name: "While-loop namespacing bug reproducer"
version: "0.0.1"
workflow_status: test
requires:
speckit_version: ">=0.8.7"
integrations:
any: ["claude"]
inputs: {}
steps:
- id: my-loop
type: while
condition: "{{ steps.my-gate.output.choice }} != 'approve'"
max_iterations: 5
body:
- id: my-gate
type: gate
prompt: "Approve, improve, rebuild, or abort?"
choices: ["approve", "improve", "rebuild", "abort"]
specify workflow run while-loop-bug-repro
# choose 'approve' on the first prompt
Expected Behavior
Loop terminates after iteration 1; condition evaluates false.
Actual Behavior
Loop continues for all max_iterations (= 5), prompting again on
every iteration. Condition evaluator on iteration 1+ reads the
iteration-0 stale output.choice.
Specify CLI Version
engine.py byte-identical (sha256 543dbb3676…) on every recent
0.8.x release; bug present in current main (0.8.12.dev0).
AI Agent
Claude Code
Operating System
Any (pure-Python engine code; not platform-specific).
Python Version
Python 3.11+
Error Logs
Not an error — silent wrong behaviour. The loop runs to
max_iterations and exits normally, ignoring the gate verdict.
Additional Context
Scope: the bug is in the workflow engine's condition resolver
(expressions.evaluate_condition), not in any integration. The
reproducer happens to use Claude as the integration the gate step
runs through, but the bug manifests identically with any agent —
the dropdown above just reflects the reproducer environment.
while and do-while are documented step kinds (engine.py:97).
If they don't work for the canonical use case (gate-driven
termination), they're effectively dead. Workflow authors today must
manually unroll review loops into nested switch-after-gate
chains, capped at 2 iterations.
Possible fix shapes
- A. Inside a loop body, expose the most recent iteration's
nested-step output under an alias like
{{ steps.my-gate.latest.output.choice }}.
- B. Auto-resolve unprefixed step refs in loop conditions to
the latest namespaced version. Backward-compatible — existing
condition text starts working.
- C. Expose iteration counter as
{{ context.iteration }} and
let condition authors reference iteration-suffixed IDs manually.
B is least friction. Want shape agreement before opening a PR
(engine internals).
AI disclosure: drafted with Claude Opus including reproducer +
analysis. Human-reviewed.
Bug Description
workflows/engine.pynamespaces nested step IDs per iteration inwhile/do-whileloops:But the loop's
condition(resolved viaexpressions.evaluate_condition→_resolve_dot_path→_build_namespace) reads step output by theunprefixed step ID. So a condition like
{{ steps.my-gate.output.choice }} != 'approve'reads theiteration-0 value on every subsequent iteration, because
steps.my-gatein the namespace points at the originalun-namespaced step output and is never updated to track
steps.my-gate:my-loop:1,:2, etc.Net: gate-driven loop termination based on a gate's verdict
never fires. The loop runs to
max_iterations.Steps to Reproduce
specify workflow run while-loop-bug-repro # choose 'approve' on the first promptExpected Behavior
Loop terminates after iteration 1; condition evaluates
false.Actual Behavior
Loop continues for all
max_iterations(= 5), prompting again onevery iteration. Condition evaluator on iteration 1+ reads the
iteration-0 stale
output.choice.Specify CLI Version
engine.pybyte-identical (sha256543dbb3676…) on every recent0.8.x release; bug present in current
main(0.8.12.dev0).AI Agent
Claude Code
Operating System
Any (pure-Python engine code; not platform-specific).
Python Version
Python 3.11+
Error Logs
Not an error — silent wrong behaviour. The loop runs to
max_iterationsand exits normally, ignoring the gate verdict.Additional Context
Scope: the bug is in the workflow engine's condition resolver
(
expressions.evaluate_condition), not in any integration. Thereproducer happens to use Claude as the integration the gate step
runs through, but the bug manifests identically with any agent —
the dropdown above just reflects the reproducer environment.
whileanddo-whileare documented step kinds (engine.py:97).If they don't work for the canonical use case (gate-driven
termination), they're effectively dead. Workflow authors today must
manually unroll review loops into nested
switch-after-gatechains, capped at 2 iterations.
Possible fix shapes
nested-step output under an alias like
{{ steps.my-gate.latest.output.choice }}.the latest namespaced version. Backward-compatible — existing
condition text starts working.
{{ context.iteration }}andlet condition authors reference iteration-suffixed IDs manually.
B is least friction. Want shape agreement before opening a PR
(engine internals).
AI disclosure: drafted with Claude Opus including reproducer +
analysis. Human-reviewed.