Repository navigation
Description
Problem Statement
The workflow shell step hardcodes a 300-second execution timeout with no way to override it. In init.py, ShellStep.execute runs:
proc = subprocess.run(
run_cmd, shell=True, capture_output=True, text=True, cwd=cwd, timeout=300,
)and the step config is only ever read for run and output_format (in both execute and validate). Any shell command that legitimately takes longer than 5 minutes — a full build, a linter aggregator, an integration-test target — is killed with TimeoutExpired, which the step maps to StepResult(status=FAILED, output={"exit_code": -1, "stderr": "timeout"}), failing the entire run.
This makes shell steps unusable as a probe/gate over real project QA commands. Concretely: a step running a repo's make code-qa (a MegaLinter/golangci-lint aggregator + build + tests, ~10+ min) fails at exactly 300s regardless of whether the command itself would pass, and there is no YAML knob to raise the limit.
Proposed Solution
Add an optional timeout field (seconds) to the shell step, defaulting to the current 300 for backward compatibility:
- id: qa
type: shell
timeout: 1800 # seconds; optional, default 300
run: make code-qaThe change is minimal and additive:
timeout = config.get("timeout", 300)
proc = subprocess.run(
run_cmd, shell=True, capture_output=True, text=True, cwd=cwd, timeout=timeout,
)plus a validate check that timeout, when present, is a positive number. (Optionally timeout: 0/null could mean "no timeout" for intentionally long-running steps, though requiring an explicit positive value may be preferable to avoid runaway steps.)
Alternatives Considered
Global env var (e.g. SPECKIT_SHELL_TIMEOUT): coarser than per-step and easy to forget; a per-step field is more precise. Could be layered in as a lower-precedence default.
Component
Specify CLI (initialization, commands)
AI Agent (if applicable)
None
Use Cases
No response
Acceptance Criteria
- A
timeout:value on a shell step overrides the 300s default and is honored bysubprocess.run. - Omitting
timeout:preserves the current behavior (300s). validaterejects a non-positive or non-numerictimeout.- A test asserts the configured value is threaded through (and/or that a step which would exceed the default succeeds when the timeout is raised).
Additional Context
- Per-step timeouts are standard in comparable systems (e.g. GitHub Actions
timeout-minutes), so this is an idiomatic, non-breaking addition. shellis the only step type with a hardcoded wall-clock limit;commandsteps (agent subprocess) have no equivalent cap — so long-running work is blocked only when it's a plain shell command.- Source: init.py (as of
0.12.5.dev0).
Activity
marcelsafin commentedon Jul 8, 2026
I'd like to fix this. Plan: optional
timeoutfield on the shell step, read viaconfig.get("timeout", 300), plus avalidatecheck that rejects non-positive or non-numeric values. Tests cover default, override, and validation failure.feat(workflows): make shell step timeout configurable (github#3404)
feat(workflows): make shell step timeout configurable (#3327) (#3328)
feat(workflows): make shell step timeout configurable (github#3327) (g…