Skip to content

fix(workflows): reject non-list input 'enum' instead of crashing#3601

Merged
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/input-enum-noniterable-crash-v2
Jul 21, 2026
Merged

fix(workflows): reject non-list input 'enum' instead of crashing#3601
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/input-enum-noniterable-crash-v2

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Problem

A workflow input whose enum is not a list slips past validation and crashes at run time:

inputs:
  scope:
    type: string
    enum: 5          # scalar instead of a list
  • Runtime crash: _resolve_inputs_coerce_input runs value not in enum_values, and x not in 5 raises a raw TypeError: argument of type 'int' is not iterable.
  • Broken validator contract: the same TypeError escapes validate_workflow's except ValueError, breaking its documented "return a list of errors, never raise" contract — so authoring-time validation crashes instead of reporting an error.
  • Silently-wrong string case: a bare string enum: "abc" doesn't crash but turns enum membership into a substring test (value in "abc"), silently accepting "a", "b", "ab", etc.

This is the same unvalidated-execute() crash class as #3482 (fan-in wait_for) and #3537 (fan-out step template): validate() should reject the value, but execute() accepts unvalidated definitions, so the bad value still reaches the engine.

Fix

  • _coerce_input now requires enum to be a list (or None), raising a clean ValueError with a clear message for any other shape. Both the validation path and runtime resolution now fail fast.
  • validate_workflow checks the enum shape directly, not only via the default-coercion path — that path only runs when a default is present, so a defaultless bad enum would otherwise slip through. A malformed enum is stripped before coercing the default so the wrong-typed-default error isn't duplicated as an enum-shape error.
  • The integration: auto sentinel only strips a list enum before coercion (enum membership is a runtime concern for auto). A non-list enum stays in the definition so it's rejected rather than silently exempted.

Behavior

Before → TypeError escapes / silent substring match.
After → Input 'scope' has invalid 'enum': must be a list, got int. at both authoring and run time.

Tests

New coverage across all three layers:

  • _coerce_input directly (scalar, bool, string, dict; valid list and None still work)
  • validate_workflow with no default present (the path the default-coercion check misses)
  • runtime _resolve_inputs
  • the integration: auto interaction (non-list enum still rejected)

Full tests/test_workflows.py passes locally except the pre-existing Windows symlink-guard tests, which require elevation and are unrelated to this change.

🤖 Generated with Claude Code

A workflow input whose `enum` is a scalar or string (e.g. `enum: 5`,
`enum: "abc"`) previously slipped past `validate_workflow` and crashed
at run time. The `value not in enum_values` membership test in
`_coerce_input` raises a raw `TypeError` ("argument of type 'int' is
not iterable") for a scalar, and a bare string turns enum membership
into a silent substring test. The `TypeError` also escapes
`validate_workflow`'s `except ValueError`, breaking its documented
"return a list of errors, never raise" contract.

This is the same unvalidated-`execute()` crash class as the fan-in
`wait_for` (github#3482) and fan-out step-template (github#3537) fixes: `validate()`
should reject the value, but the value can still reach the engine via
`execute()`, which accepts unvalidated definitions.

Fix:
- `_coerce_input` requires a list `enum` (or `None`), raising a clean
  ValueError for any other shape — so both `validate_workflow` and
  runtime `_resolve_inputs` fail fast with a clear message.
- `validate_workflow` checks `enum` shape directly (not only via the
  default-coercion path, which is reached only when a `default` exists),
  and strips a malformed `enum` before coercing the default so the
  wrong-typed-default error is not duplicated as an enum-shape error.
- The `integration: auto` sentinel only strips a *list* `enum`; a
  non-list `enum` stays in the definition so it is rejected rather than
  silently exempted by the `auto` membership skip.

Tests cover all three layers: `_coerce_input` directly, authoring-time
`validate_workflow` (with no default present), and runtime
`_resolve_inputs`, plus the `integration: auto` interaction.

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

Rejects malformed workflow input enums during validation and runtime resolution, preventing raw TypeError crashes and substring matching.

Changes:

  • Enforces list-or-None enum values.
  • Preserves integration: auto handling without exempting malformed enums.
  • Adds validation, coercion, and runtime regression tests.
Show a summary per file
File Description
src/specify_cli/workflows/engine.py Validates enum shape and safely handles default coercion.
tests/test_workflows.py Covers malformed enums across validation and runtime paths.

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 d7699c3 into github:main Jul 21, 2026
12 checks passed
@mnriem

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