Work type
Bug / regression
Observed behavior
PR #1057 hosted test-fast run 30592451360 failed
TestSourceManagerRunWorkflowFailsOnProcessExit. The compiled child exits with
status 7, but runSourceWorkflow returns write |1: broken pipe from
stdin.Close() rather than the more informative non-zero process-exit error.
The test therefore receives a failed workflow whose error does not contain
exited.
Expected behavior
When a source-workflow child exits non-zero, the workflow must report the
process exit (including bounded stderr) even if closing its stdin concurrently
also reports a broken pipe. A standalone stdin-close failure with a successful
child must remain visible.
Reproduction
Hosted artifact: PR #1057, Actions run 30592451360,
TestSourceManagerRunWorkflowFailsOnProcessExit, where the returned error was
write |1: broken pipe.
Focused command on exact pre-fix main:
go test ./internal/workflow -run '^TestSourceManagerRunWorkflowFailsOnProcessExit$' -count=100
The focused stress passed locally; the hosted failure is scheduling-dependent.
Source inspection deterministically shows the precedence bug:
internal/workflow/source.go records both closeErr and waitErr, then returns
closeErr before inspecting waitErr.
User and operational impact
Source-workflow failures can surface a transport cleanup symptom instead of the
real child exit and bounded stderr. This obscures diagnosis and makes the
accepted CI baseline intermittent, blocking otherwise valid cron/callback
promotion work.
Suspected seam and search evidence
Owning seam: SourceManager.runSourceWorkflow in
internal/workflow/source.go, after serveProtocol, stdin.Close, and
cmd.Wait.
Repository search:
rg -n "broken pipe|cmd.Wait|stdin|StdinPipe|process exited|exited" internal/workflow
closeErr is returned before waitErr.
- Existing exit and bounded-stderr tests already prove the intended process
failure contract, but do not deterministically force both errors at once.
Blast-radius impact map
- Callers/data flow: source-workflow child process, protocol pipe teardown, run
terminal error.
- Config/env/defaults: none.
- API/CLI/wire formats/tools: error text and diagnostic precedence only.
- Persistence/schema/cache: none.
- Concurrency/lifecycle: affected; simultaneous child exit and pipe close.
- Security/auth/privacy: bounded stderr handling must remain bounded.
- TUI/web/macOS: they display the corrected terminal workflow error.
- Provider/model/catalog: none.
- Deployment/observability: clearer failure evidence and stable hosted gate.
- Compatibility: successful workflows and timeout/protocol-error precedence
must remain unchanged.
- Documentation: issue plan, impact map, logs, and plans index.
Regression test first
Add a deterministic test seam or fixture that causes the child to exit non-zero
before stdin close, proving both waitErr and a broken-pipe closeErr exist.
The red assertion must show the current code returns only broken pipe.
Keep false-positive controls for:
- non-zero child exit wins and includes bounded stderr;
- protocol error still wins over teardown errors;
- deadline still reports timeout;
- a successful child with only a close failure still reports that close error;
- successful result behavior is unchanged.
Fix boundaries
In scope: deterministic regression coverage and error-precedence ordering in
runSourceWorkflow.
Out of scope: changing the workflow protocol, ignoring all pipe errors, adding
sleeps/retries, or broadly changing process supervision.
Verification plan
- Deterministic red for simultaneous close and process-exit errors.
- Focused normal and race stress at
-count=100.
- Complete
go test ./internal/workflow/....
- Complete
go test -race ./internal/workflow/....
- Unchanged foreground non-TTY
./scripts/test-regression.sh.
- Hosted
test-fast and test-race.
Rollout and rollback
Small runtime error-precedence fix. No migration or data repair. Revert if it
masks a protocol or timeout error, drops bounded stderr, or changes successful
workflow behavior.
Documentation and handoff
Add an issue-specific plan and impact map; update engineering log, long-term
thinking log, and plans index. Related to epic #1000 because it blocks the
assembled cron/callback proof.
Definition of done
Process acknowledgement
I will preserve the failing regression, update this issue if the root cause
changes, and record any newly discovered independent bug separately.
Work type
Bug / regression
Observed behavior
PR #1057 hosted
test-fastrun30592451360failedTestSourceManagerRunWorkflowFailsOnProcessExit. The compiled child exits withstatus 7, but
runSourceWorkflowreturnswrite |1: broken pipefromstdin.Close()rather than the more informative non-zero process-exit error.The test therefore receives a failed workflow whose error does not contain
exited.Expected behavior
When a source-workflow child exits non-zero, the workflow must report the
process exit (including bounded stderr) even if closing its stdin concurrently
also reports a broken pipe. A standalone stdin-close failure with a successful
child must remain visible.
Reproduction
Hosted artifact: PR #1057, Actions run
30592451360,TestSourceManagerRunWorkflowFailsOnProcessExit, where the returned error waswrite |1: broken pipe.Focused command on exact pre-fix main:
go test ./internal/workflow -run '^TestSourceManagerRunWorkflowFailsOnProcessExit$' -count=100The focused stress passed locally; the hosted failure is scheduling-dependent.
Source inspection deterministically shows the precedence bug:
internal/workflow/source.gorecords bothcloseErrandwaitErr, then returnscloseErrbefore inspectingwaitErr.User and operational impact
Source-workflow failures can surface a transport cleanup symptom instead of the
real child exit and bounded stderr. This obscures diagnosis and makes the
accepted CI baseline intermittent, blocking otherwise valid cron/callback
promotion work.
Suspected seam and search evidence
Owning seam:
SourceManager.runSourceWorkflowininternal/workflow/source.go, afterserveProtocol,stdin.Close, andcmd.Wait.Repository search:
rg -n "broken pipe|cmd.Wait|stdin|StdinPipe|process exited|exited" internal/workflowcloseErris returned beforewaitErr.failure contract, but do not deterministically force both errors at once.
Blast-radius impact map
terminal error.
must remain unchanged.
Regression test first
Add a deterministic test seam or fixture that causes the child to exit non-zero
before stdin close, proving both
waitErrand a broken-pipecloseErrexist.The red assertion must show the current code returns only
broken pipe.Keep false-positive controls for:
Fix boundaries
In scope: deterministic regression coverage and error-precedence ordering in
runSourceWorkflow.Out of scope: changing the workflow protocol, ignoring all pipe errors, adding
sleeps/retries, or broadly changing process supervision.
Verification plan
-count=100.go test ./internal/workflow/....go test -race ./internal/workflow/...../scripts/test-regression.sh.test-fastandtest-race.Rollout and rollback
Small runtime error-precedence fix. No migration or data repair. Revert if it
masks a protocol or timeout error, drops bounded stderr, or changes successful
workflow behavior.
Documentation and handoff
Add an issue-specific plan and impact map; update engineering log, long-term
thinking log, and plans index. Related to epic #1000 because it blocks the
assembled cron/callback proof.
Definition of done
Process acknowledgement
I will preserve the failing regression, update this issue if the root cause
changes, and record any newly discovered independent bug separately.