Skip to content

fix(workflows): fail while/do-while steps on non-list steps instead of crashing#3519

Merged
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/loop-steps-nonlist-crash
Jul 14, 2026
Merged

fix(workflows): fail while/do-while steps on non-list steps instead of crashing#3519
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/loop-steps-nonlist-crash

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Summary

WhileStep.validate() and DoWhileStep.validate() already reject a non-list steps body, but the engine's execute() path does not auto-validate — see WorkflowEngine.load_workflow, whose docstring notes the definition is "not yet validated". On an unvalidated run the body is returned as next_steps, and the engine feeds it straight into _execute_steps, which iterates it as step mappings. A non-list steps (a single mapping or scalar authoring mistake) was iterated element-wise — a dict yields its string keys, a str its characters — and raised AttributeError on .get(), taking down the whole run; the engine invokes step_impl.execute() with no surrounding try/except.

This guards both execute paths to return a FAILED StepResult naming the type error instead, mirroring the if/switch non-list-branch guards and the fan-out non-list items handling.

Changes

  • while — guard fires only when the condition is truthy (the only case where the body is dispatched). A false condition leaves a non-list steps benign and the step completes, unchanged.
  • do-while — body always dispatches on the first call, so the guard is unconditional.
  • The condition/expression is still evaluated first, so its result is surfaced in the step output for downstream context.

Tests

Added parametrized tests (dict / str / int) for:

  • while — non-list steps fails loudly when condition is true; stays benign/COMPLETED when condition is false.
  • do-while — non-list steps fails loudly (always dispatched).

All while/do-while step tests pass locally.

Follows the same pattern as the merged switch (non-mapping cases), fan-in (non-list wait_for), and if/switch non-list-branch crash guards.

…f crashing

`WhileStep.validate()` and `DoWhileStep.validate()` already reject a
non-list `steps` body, but the engine's `execute()` path does not
auto-validate (see `WorkflowEngine.load_workflow`, whose docstring notes
the definition is "not yet validated"). On an unvalidated run the body
is returned as `next_steps`, and the engine feeds it straight into
`_execute_steps`, which iterates it as step mappings. A non-list `steps`
— a single mapping or scalar authoring mistake — was iterated
element-wise (a dict yields its string keys, a str its characters) and
raised `AttributeError` on `.get()`, taking down the whole run; the
engine invokes `step_impl.execute()` with no surrounding try/except.

Guard both `execute` paths to return a FAILED StepResult naming the type
error instead, mirroring the if/switch non-list-branch and fan-out
non-list `items` handling. The do-while body always dispatches on the
first call, so its guard is unconditional; the while body only
dispatches when the condition is truthy, so its guard fires only then —
a false condition leaves a non-list `steps` benign and the step
completes, unchanged. The condition/expression is still evaluated first,
so its result is surfaced in the step output for downstream context.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds runtime guards preventing malformed loop bodies from crashing workflow execution.

Changes:

  • Fail while and do-while steps when dispatched bodies are not lists.
  • Preserve false-condition behavior for while.
  • Add parametrized regression tests.
Show a summary per file
File Description
src/specify_cli/workflows/steps/while_loop/__init__.py Validates dispatched loop bodies at runtime.
src/specify_cli/workflows/steps/do_while/__init__.py Rejects malformed mandatory loop bodies.
tests/test_workflows.py Covers dict, string, and integer bodies.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Medium

@mnriem mnriem merged commit 77ebd5f into github:main Jul 14, 2026
12 checks passed
@mnriem

mnriem commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

jawwad-ali added a commit to jawwad-ali/spec-kit that referenced this pull request Jul 15, 2026
IfThenStep.execute returns config['then']/['else'] directly as
StepResult.next_steps with no check that it is a list. The engine does not
auto-validate step config before execute(), so an unvalidated run with a
non-list branch (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 instead.

Add an isinstance(list) guard returning a FAILED StepResult, completing the same
guard github#3519 added to the while/do-while steps (and that its comment already
references for the 'if' step) and mirroring the switch step's 'cases' guard.

Parametrized test feeds a str/dict/int branch and asserts FAILED (fails before:
the non-iterable next_steps crashed execution).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kanfil added a commit to tikalk/agentic-sdlc-spec-kit that referenced this pull request Jul 15, 2026
Upstream merge — 2 commits (post-0.12.15):
- While/do-while non-list steps guard (github#3519): returns FAILED instead of
  crashing on non-list 'steps' on unvalidated run, mirrors if/switch/fan-out
- PyPI install docs (github#3425/github#3516): adopted docs additions, kept fork README
  install section (fork installs from tikalk repo, not PyPI)

1 conflict resolved:
- README.md: kept fork tikalk install instructions, dropped upstream PyPI block

613 tests pass. Smoke test confirms 0.12.15+adlc2.

Assisted-by: opencode (model: glm-5.2, autonomous)
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.

3 participants