Skip to content

fix(workflows): fail if/switch steps on non-list branch instead of crashing#3515

Open
Noor-ul-ain001 wants to merge 2 commits into
github:mainfrom
Noor-ul-ain001:fix/branch-steps-nonlist-crash
Open

fix(workflows): fail if/switch steps on non-list branch instead of crashing#3515
Noor-ul-ain001 wants to merge 2 commits into
github:mainfrom
Noor-ul-ain001:fix/branch-steps-nonlist-crash

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Summary

IfThenStep.validate() and SwitchStep.validate() already reject a non-list branch (then/else, and case/default), 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 selected branch is fed straight into next_steps, which _execute_steps iterates as step mappings. A non-list branch (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 switch non-mapping cases guard (#3481) and the fan-out non-list items handling.

Changes

  • if_then — guard both then and else branches at execute time.
  • switch — guard case and default branches via a shared _non_list_branch_failure helper.
  • A missing else/default still defaults to an empty list (COMPLETED), unchanged; the guard fires only on an explicit non-list value.
  • The condition/expression is still evaluated first, so its result is surfaced in the step output for downstream context.

Tests

Added parametrized tests for non-list then and else (dict / str / int). Full workflow logic suite passes locally.

Follows the same pattern as #3481 (switch non-mapping cases) and #3482 (fan-in non-list wait_for).

…ashing

`IfThenStep.validate()` and `SwitchStep.validate()` already reject a
non-list branch (`then`/`else`, and `case`/`default`), 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 selected branch is fed
straight into `next_steps`, which `_execute_steps` iterates as step
mappings. A non-list branch — 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 switch non-mapping `cases` and fan-out
non-list `items` handling. The switch guard is factored into a shared
`_non_list_branch_failure` helper covering both `case` and `default`
branches. A missing `else`/`default` still defaults to an empty list
(COMPLETED), unchanged; the guard fires only on an explicit non-list
value. 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 validation for malformed conditional workflow branches to fail steps cleanly instead of crashing execution.

Changes:

  • Guards non-list if branches.
  • Guards non-list switch case/default branches.
  • Adds malformed then/else tests.
Show a summary per file
File Description
src/specify_cli/workflows/steps/if_then/__init__.py Validates selected branch types during execution.
src/specify_cli/workflows/steps/switch/__init__.py Validates selected case and default branch types.
tests/test_workflows.py Tests malformed if-branch execution.

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

# ``.get()``. ``validate`` already rejects a non-list branch; fail this
# step loudly on an unvalidated run instead, mirroring the switch/fan-out
# steps. A missing ``else`` defaults to ``[]`` and stays valid.
if not isinstance(branch, list):
Comment thread src/specify_cli/workflows/steps/switch/__init__.py Outdated
)
for case_key, case_steps in cases.items():
if str(case_key) == str_value:
if not isinstance(case_steps, list):

@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

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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