Skip to content

fix(workflows): guard the shell step's timeout check against OverflowError - #3865

Merged
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/shell-timeout-overflow
Jul 29, 2026
Merged

fix(workflows): guard the shell step's timeout check against OverflowError#3865
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/shell-timeout-overflow

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Problem

PR #3847 hardened the prompt step's timeout guard against a huge-int value, but its twin in the shell step — the step the prompt one was mirrored from — still has the hole.

math.isfinite(10**400) raises OverflowError: int too large to convert to float. A 400-digit YAML scalar is an int and is not a bool, so it clears every clause before isfinite() and raises there, escaping _timeout_error() as exactly the uncaught crash that helper exists to prevent.

steps:
  - id: qa
    type: shell
    run: echo hi
    timeout: 1000...0   # 400 digits
$ specify workflow run wf.yml
Traceback (most recent call last):
  ...
  File "src/specify_cli/workflows/engine.py", line 361, in _validate_steps
    step_errors = step_impl.validate(step_config)
  File "src/specify_cli/workflows/steps/shell/__init__.py", line 127
    or not math.isfinite(timeout)
OverflowError: int too large to convert to float

workflow_run calls engine.validate() before executing any step, so the OverflowError propagates out of validate_workflow and kills the command with a bare traceback that names neither the step nor the field, instead of the "Workflow validation failed" report.

execute() shares the same helper, so an unvalidated run raises there too — and the engine re-raises anything a step throws, aborting the whole workflow after earlier steps have already run their side effects.

The value is genuinely invalid rather than merely unrepresentable in the check: subprocess.run(timeout=10**400) raises the same OverflowError.

Unlike the prompt step, the shell step checks isfinite() before timeout <= 0, so a negative huge int (-(10**400)) crashes as well rather than being caught by the sign check.

Fix

Wrapped the condition in try/except OverflowError and treated the value as invalid, mirroring the prompt step's guard so both steps reject the same values with the same message. Now:

Workflow validation failed:
  - Shell step 'qa': 'timeout' must be a positive number of seconds, got 1000...0.

caught before the first step runs. Valid int/float timeouts, non-finite floats, bools, strings and non-positive values are unaffected — the existing clauses are unchanged.

Tests

Regression tests in TestShellStep:

  • test_validate_rejects_huge_int_timeoutvalidate() rejects both signs of the huge int.
  • test_validate_workflow_reports_huge_int_timeoutvalidate_workflow() reports it end to end, pinning the path the CLI actually takes rather than just the helper.
  • test_execute_fails_cleanly_on_huge_int_timeoutexecute() fails only that step, with subprocess.run patched to assert it is never reached.

Test-the-test: reverting the source change fails all three with OverflowError and leaves the rest of TestShellStep passing.

pytest tests/test_workflows.py shows no new failures against the baseline on this platform (the pre-existing Windows symlink-elevation and tmpdir failures are unchanged in count); the TestShellStep/TestPromptStep selection goes from 50 to 53 passing.

🤖 Generated with Claude Code

…Error

PR github#3847 hardened the prompt step's `timeout` guard against a huge-int
value, but its twin in the shell step — the step the prompt one was
mirrored from — still has the hole.

`math.isfinite(10**400)` raises `OverflowError: int too large to convert
to float`. A 400-digit YAML scalar is an `int` and is not a `bool`, so it
clears every clause before `isfinite()` and raises there, escaping
`_timeout_error()` as exactly the uncaught crash that helper exists to
prevent:

    steps:
      - id: qa
        type: shell
        run: echo hi
        timeout: 1000...0   # 400 digits

    $ specify workflow run wf.yml
    Traceback (most recent call last):
      ...
      File "src/specify_cli/workflows/engine.py", line 361, in _validate_steps
        step_errors = step_impl.validate(step_config)
      File "src/specify_cli/workflows/steps/shell/__init__.py", line 127
        or not math.isfinite(timeout)
    OverflowError: int too large to convert to float

`workflow_run` calls `engine.validate()` before executing any step, so
the OverflowError propagates out of `validate_workflow` and kills the
command with a bare traceback that names neither the step nor the field,
instead of the "Workflow validation failed" report. `execute()` shares
the same helper, so an unvalidated run raises there too — and the engine
re-raises anything a step throws, aborting the whole workflow after
earlier steps have already run their side effects. The value is
genuinely invalid rather than merely unrepresentable in the check:
`subprocess.run(timeout=10**400)` raises the same OverflowError.

Unlike the prompt step, the shell step checks `isfinite()` *before*
`timeout <= 0`, so a negative huge int (`-(10**400)`) crashes as well
rather than being caught by the sign check.

Wrapped the condition in `try/except OverflowError` and treated the
value as invalid, mirroring the prompt step's guard so both steps reject
the same values with the same message. Now:

    Workflow validation failed:
      - Shell step 'qa': 'timeout' must be a positive number of seconds,
        got 1000...0.

Valid int/float timeouts, non-finite floats, bools, strings and
non-positive values are unaffected — the existing clauses are unchanged.

Regression tests in `TestShellStep`: `validate()` rejects both signs of
the huge int, `validate_workflow()` reports it end to end (pinning the
path the CLI actually takes, not just the helper), and `execute()` fails
only that step with `subprocess.run` patched to assert it is never
reached. Test-the-test: reverting the source change fails all three with
`OverflowError` and leaves the rest of `TestShellStep` passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Assisted-by: Claude Code (model: claude-opus-5, under direct human supervision)

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

Guards shell-step timeout validation against huge integers that cause math.isfinite() to raise OverflowError.

Changes:

  • Treats overflowing timeout values as invalid.
  • Adds helper, workflow-level, and execution regression tests.
Show a summary per file
File Description
src/specify_cli/workflows/steps/shell/__init__.py Handles oversized integer timeouts safely.
tests/test_workflows.py Covers validation and execution 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 6712665 into github:main Jul 29, 2026
14 checks passed
@mnriem

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