Skip to content

fix(workflows): fail fan-out loudly on a truthy non-mapping step template#3537

Merged
mnriem merged 2 commits into
github:mainfrom
Noor-ul-ain001:fix/fanout-nondict-template-crash
Jul 17, 2026
Merged

fix(workflows): fail fan-out loudly on a truthy non-mapping step template#3537
mnriem merged 2 commits into
github:mainfrom
Noor-ul-ain001:fix/fanout-nondict-template-crash

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

@

Summary

A fan-out step whose step: field is a truthy scalar or list (an authoring mistake) passed execute(), produced a COMPLETED result, and reached the engine — which calls template.get("id", ...) in _run_fan_out (engine.py, guarded only by if template and items:). A non-dict step raised AttributeError there, with no surrounding try/except, taking down the whole run instead of failing the single step.

validate() already rejects a non-mapping step, but the engine does not auto-validate (see WorkflowEngine.load_workflow), so an unvalidated run crashed. This is the same bug class as the switch non-dict cases and fan-in non-list wait_for guards.

Changes

  • fan_out/__init__.py: guard execute() to FAIL the step with a clear error and normalized empty output when step is not a mapping — mirroring the existing non-list items guard and the switch cases guard.
  • tests/test_workflows.py: add test_execute_non_dict_step_fails_loudly covering list/scalar/int step values. (validate was already covered by test_validate_step_not_mapping; the execute()-path guard was not.)

Testing

All 25 fan-out tests pass locally.

🤖 Generated with Claude Code
@

…late

A fan-out step whose `step:` is a truthy scalar or list (an authoring mistake) passed execute and reached the engine, which calls template.get("id", ...) in _run_fan_out — raising AttributeError and taking down the whole run. validate already rejects a non-mapping step, but the engine does not auto-validate, so an unvalidated run crashed.

Guard execute to FAIL the step (with a clear error and normalized empty output) instead, mirroring the existing non-list items guard and the switch non-dict cases guard. Add the matching test_execute_non_dict_step_fails_loudly covering the execute-path guard (validate was already covered).

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

Prevents malformed fan-out step templates from crashing workflow runs.

Changes:

  • Rejects non-dictionary fan-out templates during execution.
  • Adds regression coverage for list and scalar templates.
Show a summary per file
File Description
src/specify_cli/workflows/steps/fan_out/__init__.py Adds the runtime type guard.
tests/test_workflows.py Tests malformed template handling.

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: 1
  • Review effort level: Medium

Comment thread src/specify_cli/workflows/steps/fan_out/__init__.py

@mnriem mnriem left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please address Copilot feedback

Noor-ul-ain001 added a commit to Noor-ul-ain001/spec-kit that referenced this pull request Jul 17, 2026
fix(workflows): reject explicit fan-out `step: null` in validate()

The runtime guard in execute() rejects a truthy non-mapping step, but
`config.get("step", {})` only substitutes the `{}` default for an *absent*
key — an explicit `step: null` reaches the guard as None and FAILS the step.
validate() previously exempted None (`step is not None and ...`), so such a
workflow passed validation and then failed during execution.

Align validate() with the runtime guard: a present-but-non-mapping `step`
(including `None`) is an authoring mistake and is now rejected up front.
Extend the validate and execute regression cases to cover None.

Addresses Copilot review feedback on github#3537.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
Noor-ul-ain001 added a commit to Noor-ul-ain001/spec-kit that referenced this pull request Jul 17, 2026
fix(workflows): reject explicit fan-out `step: null` in validate()

The runtime guard in execute() rejects a truthy non-mapping step, but
`config.get("step", {})` only substitutes the `{}` default for an *absent*
key — an explicit `step: null` reaches the guard as None and FAILS the step.
validate() previously exempted None (`step is not None and ...`), so such a
workflow passed validation and then failed during execution.

Align validate() with the runtime guard: a present-but-non-mapping `step`
(including `None`) is an authoring mistake and is now rejected up front.
Extend the validate and execute regression cases to cover None.

Addresses Copilot review feedback on github#3537.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
@Noor-ul-ain001
Noor-ul-ain001 force-pushed the fix/fanout-nondict-template-crash branch from af67f3d to c3fa1bf Compare July 17, 2026 05:12
The runtime guard in execute() rejects a truthy non-mapping step, but
`config.get("step", {})` only substitutes the `{}` default for an *absent*
key — an explicit `step: null` reaches the guard as None and FAILS the step.
validate() previously exempted None (`step is not None and ...`), so such a
workflow passed validation and then failed during execution.

Align validate() with the runtime guard: a present-but-non-mapping `step`
(including `None`) is an authoring mistake and is now rejected up front.
Extend the validate and execute regression cases to cover None.

Addresses Copilot review feedback on github#3537.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Noor-ul-ain001
Noor-ul-ain001 force-pushed the fix/fanout-nondict-template-crash branch from c3fa1bf to c3822b8 Compare July 17, 2026 05:18
@mnriem
mnriem requested a review from Copilot July 17, 2026 13:28

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.

Review details

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

@mnriem
mnriem self-requested a review July 17, 2026 14:12
@mnriem
mnriem merged commit c1e5cfa 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