Relationships
#1013 command/shell: step with timeout + a surviving child process reports exit -1 despite command exiting 0
Opened by atalanta · 7/7/2026· Shipped 7/7/2026
Summary
A command/shell (command/shell type, typeVersion 2026.02.09.1) execute
step run inside a workflow reports Command exited with code -1 (step failed)
when BOTH conditions hold:
- the step has a
timeoutset, AND - the command leaves a background/child process alive at the moment it exits.
The command's own exit code is 0 (the recorded result.exitCode is 0), and the
same command succeeds when run without a timeout. The -1 therefore comes
from the timeout wrapper's process handling, not from the command.
Environment
- swamp: 20260626.005336.0-sha.f302d18e
- command/shell typeVersion: 2026.02.09.1
- macOS (Darwin), zsh
Minimal reproduction
A one-step workflow calling command/shell.execute:
jobs:
- name: main
steps:
- name: spawn
task:
type: model_method
modelIdOrName: <a command/shell instance>
methodName: execute
inputs:
run: "sleep 2 & exit 0" # backgrounds a child, then exits 0
workingDir: '.'
timeout: 900
ignoreExitCode: falseRun it: the step FAILS with Command exited with code -1.
Remove the timeout: 900 line (leave everything else): the step SUCCEEDS.
Characterisation (isolated)
timeout+ trivial command, no child (echo hi; exit 0) -> SUCCEEDStimeout+ backgrounded child (sleep 2 & exit 0) -> FAILS -1timeout+cargo test(spawns test/subprocesses) -> FAILS -1- NO
timeout+ any of the above -> SUCCEEDS - The recorded
result.exitCodeis 0 even when the step is marked failed.
Expected
With ignoreExitCode: false, the step outcome should reflect the command's own
exit status (0 -> success). A timeout that is not exceeded should not change
the outcome, regardless of whether the command spawned a child that is still
alive at exit.
Likely cause / hypothesis
The timeout path appears to run the command in a process group and, on completion, waits on / signals the group and reads a child's status (or the group-kill signal) as the step's exit code (-1) instead of the direct child's exit code. Non-timeout path does not, hence the difference.
Impact
Any command/shell step with a timeout that runs a test suite or tool which
spawns subprocesses (very common) fails spuriously, so timeout cannot be used
as a guard on such steps. Workaround: omit timeout (loses the guard) or invoke
the model method directly outside a workflow (loses gating).
Shipped
Click a lifecycle step above to view its details.