Skip to content

fix(workflows): validate non-mapping options in command step#3496

Open
thejesh23 wants to merge 1 commit into
github:mainfrom
thejesh23:fix/3493-command-options-validation
Open

fix(workflows): validate non-mapping options in command step#3496
thejesh23 wants to merge 1 commit into
github:mainfrom
thejesh23:fix/3493-command-options-validation

Conversation

@thejesh23

Copy link
Copy Markdown

Fixes #3493.

What

CommandStep.execute() merges step-level options into the workflow-level defaults via dict.update(), but CommandStep.validate() did not check the type of options, and the merge itself was gated only on truthiness (if step_options:). Any truthy non-mapping value therefore passed validation and crashed at run time:

options: [1, 2]   -> TypeError: cannot convert dictionary update sequence element #0 to a sequence
options: "foo"    -> ValueError: dictionary update sequence element #0 has length 1; 2 is required

Sibling of #3492 (same file, same class of type-validation gap in the command step).

Fix

Same shape as the fix already applied in the shell / while-loop / do-while validators and in #3492:

  • validate() rejects a non-mapping options with a message that names the field.
  • execute() guards the .update() with isinstance(step_options, dict) so a caller that bypasses WorkflowEngine.validate() (e.g. ad-hoc step.execute(...)) still fails cleanly and the workflow-level defaults survive.

Tests

Two new regression tests in TestCommandStep:

  • test_validate_rejects_non_mapping_options — a list and a string both report a clear 'options' must be a mapping error.
  • test_execute_non_mapping_options_does_not_crash — the validate-bypass path returns StepStatus.FAILED and the workflow-level defaults are preserved unchanged (a malformed step-level override is discarded, not partially applied).

Targeted run:

$ .venv/bin/python -m pytest tests/test_workflows.py::TestCommandStep -q
============================== 12 passed in 0.35s ==============================

Manual test results

Agent: N/A — workflow-engine-only fix, no slash-command surface changed. | OS/Shell: macOS 15 / zsh

Command tested Notes
/speckit.plan Not affected — CLI dispatch surface unchanged. TestCommandStep::test_options_merge still passes (well-formed step options are still merged over defaults).

Only touches src/specify_cli/workflows/steps/command/__init__.py; no templates/commands/*, no scripts/bash|powershell/*, no src/specify_cli/* CLI entry point. Per CONTRIBUTING.md's mapping rules, no slash command's behavior changes.

Relation to #3495

#3495 (input validation, closes #3492) is an independent sibling fix in the same file. Both share the pattern the recent fix(workflows) commits have been applying elsewhere. They land cleanly on top of each other; happy to rebase either way.

AI disclosure

Per CONTRIBUTING.md#ai-contributions-in-spec-kit: this PR (bug identification, patch, and tests) was prepared with Claude (Anthropic) assistance in an autonomous agent session. Reproducer executed against a fresh clone of main; the fix follows the existing type-validation pattern in sibling step validators; the workflow test suite was run green before submission. The change is minimal — one isinstance guard in execute(), one check in validate(), two focused tests.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings July 13, 2026 17:34
@thejesh23 thejesh23 requested a review from mnriem as a code owner July 13, 2026 17:34

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 type validation for command-step options to prevent runtime dict.update() crashes.

Changes:

  • Rejects non-mapping options during validation.
  • Adds defensive execution handling and regression tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/specify_cli/workflows/steps/command/__init__.py Validates and guards option merging.
tests/test_workflows.py Tests malformed option validation and execution.

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

Comment on lines 57 to 58
if isinstance(step_options, dict) and step_options:
options.update(step_options)

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

Comment thread src/specify_cli/workflows/steps/command/__init__.py Outdated
Comment thread tests/test_workflows.py Outdated
thejesh23 added a commit to thejesh23/spec-kit that referenced this pull request Jul 13, 2026
Address Copilot review on github#3496. The prior guard silently skipped the
merge for a non-mapping ``options`` and still called ``_try_dispatch``,
so with an installed CLI a malformed step could return ``COMPLETED``
instead of the clean field-specific failure the PR claimed. The
regression test also masked this by forcing ``shutil.which`` to ``None``,
which made the observed ``FAILED`` unrelated to ``options``.

Changes:

- ``execute()`` returns a field-named ``FAILED`` result *before*
  dispatch when ``options`` is not a mapping. Workflow-level defaults
  are preserved in ``output.options`` and ``dispatched`` is ``False``.
- ``test_execute_non_mapping_options_fails_before_dispatch`` (renamed)
  now mocks ``_try_dispatch`` to a *success* result so an accidental
  dispatch would surface as ``COMPLETED``, and asserts the dispatch
  mock is never called and the error string names ``'options'``.
- ``test_validate_rejects_non_mapping_options`` covers ``None`` (a
  bare YAML ``options:``), matching the documented behavior.

Refs github#3493.
Copilot AI review requested due to automatic review settings July 13, 2026 20:50

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

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@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 resolve conflicts

Address Copilot review on github#3496. The prior guard silently skipped the
merge for a non-mapping ``options`` and still called ``_try_dispatch``,
so with an installed CLI a malformed step could return ``COMPLETED``
instead of the clean field-specific failure the PR claimed. The
regression test also masked this by forcing ``shutil.which`` to ``None``,
which made the observed ``FAILED`` unrelated to ``options``.

Changes:

- ``execute()`` returns a field-named ``FAILED`` result *before*
  dispatch when ``options`` is not a mapping. Workflow-level defaults
  are preserved in ``output.options`` and ``dispatched`` is ``False``.
- ``test_execute_non_mapping_options_fails_before_dispatch`` (renamed)
  now mocks ``_try_dispatch`` to a *success* result so an accidental
  dispatch would surface as ``COMPLETED``, and asserts the dispatch
  mock is never called and the error string names ``'options'``.
- ``test_validate_rejects_non_mapping_options`` covers ``None`` (a
  bare YAML ``options:``), matching the documented behavior.

Refs github#3493.
Copilot AI review requested due to automatic review settings July 14, 2026 01:48
@thejesh23 thejesh23 force-pushed the fix/3493-command-options-validation branch from 3b444b2 to ed12de0 Compare July 14, 2026 01:48

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

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@thejesh23 thejesh23 requested a review from mnriem July 14, 2026 02:06
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.

fix(workflows): command step crashes with TypeError on non-mapping options fix(workflows): command step crashes with AttributeError on input: null

3 participants