Skip to content

fix(workflows): fail fan-in loudly on a non-string wait_for entry#3579

Merged
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/fan-in-wait-for-element-crash
Jul 17, 2026
Merged

fix(workflows): fail fan-in loudly on a non-string wait_for entry#3579
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/fan-in-wait-for-element-crash

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Summary

FanInStep.execute guards a non-list wait_for (added in #3482), and the engine's load-time validation (engine.py) rejects non-string wait_for entries. But the engine does not auto-validate step config before running it — the same "unvalidated run" scenario the existing non-list guard is written for — so execute iterated the list's elements raw and hit two failure modes:

  • Unhashable entry — a list/dict from a natural YAML indentation slip like wait_for: [[a, b]] crashed the whole run at context.steps.get(entry, ...) with a raw TypeError: cannot use 'list' as a dict key (unhashable type: 'list').
  • Hashable non-string entrywait_for: [123] silently joined an empty {} and still reported COMPLETED — the exact "silent empty result + COMPLETED" wiring bug that the whole-list guard and the engine's fan-in validation both exist to prevent.

This extends the execute() guard to reject any non-string wait_for entry, mirroring the sibling non-list guard directly above it and reusing the engine's own "entries must be step-id strings" phrasing for a consistent error.

Reproduction (against current main)

from specify_cli.workflows.steps.fan_in import FanInStep
from specify_cli.workflows.base import StepContext

step = FanInStep()
ctx = StepContext(steps={"a": {"output": {"x": 1}}})

# 1) unhashable entry -> raw TypeError takes down the run
step.execute({"id": "j", "wait_for": [["a", "b"]]}, ctx)
# TypeError: cannot use 'list' as a dict key (unhashable type: 'list')

# 2) non-string entry -> silent empty join, reported COMPLETED
r = step.execute({"id": "j", "wait_for": ["a", 123]}, ctx)
# r.status == COMPLETED, r.output["results"] == [{'x': 1}, {}]   <-- bogus {}

Changes

  • src/specify_cli/workflows/steps/fan_in/__init__.py — reject any non-string wait_for entry in execute(), failing the step cleanly with output={"results": []}.
  • tests/test_workflows.py — parametrized regression test (test_execute_non_string_wait_for_entry_fails_loudly) covering unhashable (list/dict) and hashable-non-string (int/None) entries.

Testing

  • New test fails on the pre-fix source (raw TypeError for unhashable, wrong COMPLETED for non-string) and passes with the guard ("test-the-test" verified via git stash).
  • All fan-in tests pass (23 selected).
  • Pre-existing unrelated failures: TestWorkflowCliAlignment symlink/cross-project tests fail on Windows without Developer Mode/elevation — verified identical on a clean upstream/main tree with these changes stashed.

Related

Same "unvalidated execute() crash" class as the sibling fan-in non-list guard (#3482), fan-out non-list items/step-template guards, the if/switch non-list branch guards (#3515), and the while/do-while non-list steps guards (#3519).

🤖 Generated with Claude Code

`FanInStep.execute` already guards a non-list `wait_for` (github#3482), and the
engine's load-time validation rejects non-string entries. But the engine does
not auto-validate step config, so on an unvalidated run `execute` iterated the
list's *elements* raw:

- An unhashable entry (a list/dict from a YAML indentation slip like
  `wait_for: [[a, b]]`) crashed the whole run at `context.steps.get(entry, ...)`
  with a raw `TypeError: cannot use 'list' as a dict key`.
- A hashable-but-non-string entry (`wait_for: [123]`) silently joined an empty
  `{}` and still reported COMPLETED — the exact "silent empty result +
  COMPLETED" wiring bug the whole-list guard and the engine's fan-in
  validation both exist to prevent.

Extend the execute() guard to reject any non-string entry with the engine's
"entries must be step-id strings" phrasing, mirroring the sibling non-list
guard right above it. Adds regression coverage for unhashable and
hashable-non-string entries.

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 validation so fan-in steps fail cleanly when wait_for contains non-string entries.

Changes:

  • Rejects non-string step IDs before result collection.
  • Adds regression coverage for hashable and unhashable invalid entries.
Show a summary per file
File Description
src/specify_cli/workflows/steps/fan_in/__init__.py Adds the runtime entry-type guard.
tests/test_workflows.py Tests invalid list, mapping, integer, and null entries.

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: Medium

@mnriem
mnriem merged commit 3d2901e into github:main Jul 17, 2026
12 checks passed
@mnriem

mnriem commented Jul 17, 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