fix(workflows): guard the shell step's timeout check against OverflowError - #3865
Merged
Merged
Conversation
…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)
Contributor
There was a problem hiding this comment.
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
Collaborator
|
Thank you! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
PR #3847 hardened the prompt step's
timeoutguard 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)raisesOverflowError: int too large to convert to float. A 400-digit YAML scalar is anintand is not abool, so it clears every clause beforeisfinite()and raises there, escaping_timeout_error()as exactly the uncaught crash that helper exists to prevent.workflow_runcallsengine.validate()before executing any step, so theOverflowErrorpropagates out ofvalidate_workflowand 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 sameOverflowError.Unlike the prompt step, the shell step checks
isfinite()beforetimeout <= 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 OverflowErrorand treated the value as invalid, mirroring the prompt step's guard so both steps reject the same values with the same message. Now: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_timeout—validate()rejects both signs of the huge int.test_validate_workflow_reports_huge_int_timeout—validate_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_timeout—execute()fails only that step, withsubprocess.runpatched to assert it is never reached.Test-the-test: reverting the source change fails all three with
OverflowErrorand leaves the rest ofTestShellSteppassing.pytest tests/test_workflows.pyshows no new failures against the baseline on this platform (the pre-existing Windows symlink-elevation and tmpdir failures are unchanged in count); theTestShellStep/TestPromptStepselection goes from 50 to 53 passing.🤖 Generated with Claude Code