Skip to content

fix(workflows): if/while/do-while execute() fail cleanly on a non-list branch/body#3528

Open
jawwad-ali wants to merge 1 commit into
github:mainfrom
jawwad-ali:fix/control-flow-nonlist-branch-guard
Open

fix(workflows): if/while/do-while execute() fail cleanly on a non-list branch/body#3528
jawwad-ali wants to merge 1 commit into
github:mainfrom
jawwad-ali:fix/control-flow-nonlist-branch-guard

Conversation

@jawwad-ali

Copy link
Copy Markdown
Contributor

What

IfThenStep.execute returns config['then']/['else'] and WhileStep/DoWhileStep.execute return config['steps'] directly as StepResult.next_steps, with no check that they're lists:

branch = config.get("then", [])
return StepResult(status=COMPLETED, next_steps=branch)   # branch could be "oops" / {...} / 42

The engine does not auto-validate step config before execute(), so an unvalidated run with a non-list branch/body (a scalar or mapping authoring mistake) passes a non-iterable as next_steps and crashes the whole run. validate() catches this, but execute() should fail the step loudly rather than take down the run.

Fix

Add an isinstance(..., list) guard to each execute(), returning a FAILED StepResult with a clear message — mirroring the existing guards in the sibling control-flow steps (switch's non-mapping cases, fan-out's non-list items).

Test

Parametrized test_execute_rejects_non_list_branch (if) and test_execute_rejects_non_list_steps (while, do-while) feed a str/dict/int and assert FAILED — fail before (the non-iterable next_steps crashed execution), pass after.


🤖 Written with the assistance of Claude Code (AI). Bug self-found; fix/tests verified locally (fail-before / pass-after), ruff clean.

…t branch

IfThenStep passes config['then']/['else'] and WhileStep/DoWhileStep pass
config['steps'] straight through as StepResult.next_steps without checking they
are lists. The engine does not auto-validate step config before execute(), so an
unvalidated run with a non-list branch/body (a scalar/mapping authoring mistake)
passed a non-iterable as next_steps and crashed the whole run. validate() already
rejects these, but execute() must fail loudly too.

Add an isinstance(list) guard to each execute(), returning a FAILED StepResult
with a clear message — mirroring the switch step's non-mapping 'cases' guard and
the fan-out step's non-list 'items' guard.

Parametrized tests on all three steps feed a str/dict/int branch and assert
FAILED (fail before: the non-iterable next_steps crashed execution).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jawwad-ali jawwad-ali requested a review from mnriem as a code owner July 14, 2026 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant