Skip to content

feat(workflows): make shell step timeout configurable (#3327)#3328

Open
Noor-ul-ain001 wants to merge 3 commits into
github:mainfrom
Noor-ul-ain001:feat/3327-shell-timeout
Open

feat(workflows): make shell step timeout configurable (#3327)#3328
Noor-ul-ain001 wants to merge 3 commits into
github:mainfrom
Noor-ul-ain001:feat/3327-shell-timeout

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Summary

Closes #3327. The workflow shell step hardcoded a 300-second subprocess timeout with no way to override it, so any command that legitimately runs longer than five minutes — a full build, a MegaLinter/golangci-lint aggregator, an integration-test target — was killed with TimeoutExpired and failed the entire run. There was no YAML knob to raise the limit, which made shell steps unusable as a probe/gate over real project QA commands.

Changes

  • Add an optional timeout field (seconds) to the shell step, read via config.get("timeout", 300) and threaded into subprocess.run. Defaults to 300 so existing workflows are unaffected.
  • The timeout failure message now reports the configured value instead of a hardcoded 300.
  • validate rejects a timeout that is not a positive number. bool is rejected explicitly — it is an int subclass, but timeout: true is a config error, not a duration.
- id: qa
  type: shell
  timeout: 1800        # seconds; optional, default 300
  run: make code-qa

Acceptance criteria (from the issue)

  • A timeout: value overrides the 300s default and is honored by subprocess.run.
  • Omitting timeout: preserves current behavior (300s).
  • validate rejects a non-positive or non-numeric timeout.
  • A test asserts the configured value is threaded through.

Testing

Seven new tests under TestShellStep:

  • configured timeout is passed to subprocess.run
  • default 300 preserved when omitted
  • timeout error message reflects the configured value
  • validate rejects non-positive (0, -30) and non-numeric ("30", True) values
  • validate accepts positive int/float

pytest tests/test_workflows.py::TestShellStep → 13 passed. Confirmed the behavior-changing tests fail without the source change (test-the-test via git stash).

🤖 Generated with Claude Code

The `shell` step hardcoded a 300s subprocess timeout, so any command
that legitimately runs longer than five minutes (a full build, a linter
aggregator, an integration-test target) was killed with TimeoutExpired
and failed the whole run, with no YAML knob to raise the limit.

Add an optional `timeout` field (seconds) that defaults to 300 for
backward compatibility and is threaded through to `subprocess.run`. The
timeout failure message now reports the configured value instead of a
hardcoded 300. `validate` rejects a `timeout` that is not a positive
number (bool is rejected explicitly, since it is an int subclass but a
config error rather than a duration).

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

This PR makes the workflow shell step’s subprocess timeout configurable per-step (defaulting to the historical 300s), so longer-running QA/build commands can run without being killed by a hardcoded limit.

Changes:

  • Add optional timeout (seconds) to shell step execution, defaulting to 300.
  • Update the timeout failure message to report the configured timeout.
  • Add/extend ShellStep validation and unit tests to cover timeout behavior and invalid values.
Show a summary per file
File Description
src/specify_cli/workflows/steps/shell/init.py Threads timeout from config into subprocess.run, updates timeout error message, and validates timeout input.
tests/test_workflows.py Adds TestShellStep coverage for default/configured timeout wiring, error messaging, and validation cases.

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

Comment thread src/specify_cli/workflows/steps/shell/__init__.py
Comment thread tests/test_workflows.py
Comment on lines +1318 to +1322
# A string and a bool are both invalid (bool is an int subclass but a
# config error, not a duration).
for bad in ("30", True):
errors = step.validate({"id": "qa", "run": "echo hi", "timeout": bad})
assert any("'timeout' must be a positive number" in e for e in errors)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 41cb5f1test_validate_rejects_non_finite_timeout asserts float('inf'), float('-inf'), and float('nan') are all rejected by validate(). Confirmed inf/nan slip past a plain > 0 check, so the test exercises the math.isfinite guard rather than duplicating existing coverage.

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

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Low

The isfinite guard added in 955d46a rejects YAML .inf/.nan timeouts, but no test asserted it. inf and nan are floats that pass a plain > 0 check (nan <= 0 is False), so without an explicit case a regression could silently reaccept them and crash subprocess.run(timeout=...) at runtime. Addresses the remaining Copilot review comment on PR github#3328.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.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.

[Feature]: Make the shell step's execution timeout configurable (currently hardcoded to 300s)

3 participants