Skip to content

fix(workflows): fail fan-in step on non-list wait_for instead of crashing#3482

Merged
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/fanin-nonlist-waitfor-crash
Jul 13, 2026
Merged

fix(workflows): fail fan-in step on non-list wait_for instead of crashing#3482
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/fanin-nonlist-waitfor-crash

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Summary

FanInStep.validate() and the engine's fan-in wiring checks both reject a non-list wait_for, but the engine's execute() path does not auto-validate — WorkflowEngine.load_workflow's docstring notes the returned definition is "not yet validated; call validate_workflow() or engine.validate() separately."

On such an unvalidated run, FanInStep.execute iterated the raw value (for step_id in wait_for), with two bad outcomes depending on the type:

  • Scalar (wait_for: 5, wait_for: null) → TypeError, which took down the whole run, since the engine calls step_impl.execute() with no surrounding try/except.
  • String (wait_for: stepA) → silently iterated the string's characters, returning a join of empty results with a COMPLETED status:
>>> FanInStep().execute({"id": "f", "wait_for": "stepA"}, ctx).output["results"]
[{}, {}, {}, {}, {}]   # one bogus empty result per character, COMPLETED

That silent-wrong case is exactly the "silent empty result + COMPLETED" wiring bug the engine's own fan-in validation comment (engine.py) warns against.

Fix

Guard execute to return a FAILED StepResult naming the type error — mirroring the fan-out step's non-list items handling (test_execute_non_list_items_fails_loudly) and the switch/max_iterations/timeout/fan-in-output guards. A missing wait_for key still defaults to an empty list (COMPLETED), unchanged; the guard fires only on an explicit non-list value.

Same bug class as #3481 and prior fixes: a validator catches the bad value, but the runtime path crashes (or silently misbehaves) on it when validation is skipped.

Testing

  • Added TestFanInStep::test_execute_non_list_wait_for_fails_loudly (parametrized over str, int, None, dict).
  • Full tests/test_workflows.py passes except the 11 pre-existing Windows symlink-guard tests (fail identically on a clean base; require elevation).
  • ruff check clean on the changed files.

🤖 Generated with Claude Code

…hing

`FanInStep.validate()` and the engine's fan-in checks both reject a
non-list `wait_for`, 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, `execute`
iterated the raw value with `for step_id in wait_for`, with two bad
outcomes:

  * a scalar (`wait_for: 5`, `wait_for: null`) raised `TypeError` and
    took down the whole run — the engine invokes `step_impl.execute()`
    with no surrounding try/except; and
  * a string (`wait_for: stepA`) silently iterated its characters and
    returned a join of empty results with a COMPLETED status — the exact
    "silent empty result + COMPLETED" wiring bug the engine's own fan-in
    validation comment warns against.

Guard `execute` to return a FAILED StepResult naming the type error
instead, mirroring the fan-out step's non-list `items` handling. A
missing `wait_for` key still defaults to an empty list (COMPLETED),
unchanged; the guard fires only on an explicit non-list value.

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

This PR hardens the workflows runtime by making FanInStep.execute() fail loudly (per-step) when wait_for is explicitly provided as a non-list, avoiding both run-crashing exceptions and the silent “iterate string characters” misbehavior when workflow validation is skipped.

Changes:

  • Add a runtime guard in FanInStep.execute() that returns a FAILED StepResult with a clear type error when wait_for is not a list.
  • Add a parametrized regression test covering several non-list wait_for values (str, int, None, dict).
Show a summary per file
File Description
src/specify_cli/workflows/steps/fan_in/__init__.py Adds an execute-time type guard for non-list wait_for, returning a failed step result instead of crashing or silently misjoining.
tests/test_workflows.py Adds regression coverage ensuring non-list wait_for fails the step loudly and yields an empty results output.

Review details

Tip

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

  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Low

@mnriem mnriem merged commit a965413 into github:main Jul 13, 2026
12 checks passed
@mnriem

mnriem commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

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