fix(agent): report the failures the channel paths dropped - #651
Merged
Conversation
haribo
force-pushed
the
fix/638-swallowed-failures
branch
from
September 10, 2026 09:43
1b4b412 to
2198b18
Compare
haribo
force-pushed
the
fix/638-swallowed-failures
branch
from
September 10, 2026 09:50
2198b18 to
601ded0
Compare
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.
Summary
Three dropped failures, each turning a diagnosable problem into a puzzling one later.
internal/agent/channel.go:94—accept()overwrotec.connwithout closing it.drop()is the only other closer and it runs when an ask discovers the connection is dead, so a
control host reconnecting without the agent having asked anything in between leaked a
descriptor. The agent lives up to 2h (ADR-0005): the ceiling is the number of runs in that
window, not one.
internal/agent/agent.go:35—ServeOndropped theListenerror. It now buildsUnavailable(err): the job still runs, and any ask fails naming the cause instead of sittingout
attachWaitand reportingno control host attached.internal/transport/local.go:32—os.MkdirTempdropped. Now fails the run naming whatcould not be made.
The decision this issue left open
Fail immediately, or lazily? Lazily, in the agent — but for a corrected reason. My audit
said "a dry-run reaches no primitive"; the sharper fact is that
controlChannel(
cmd/shellf/main.go:245) decides from the plan, statically, so a plan declaring a primitiveinside an
applygets a channel a--dry-runnever uses. Failing atListenwould break thatrun.
Not lazily in the local transport, and the asymmetry is deliberate: the agent is another
process reading its workdir from argv, so there is no
Unavailableto hand it — continuingthere guarantees the cause is lost.
A fourth site, same defect
internal/agent/resident.go:40has the identical shape, and it is the main path (ADR-0005),not one of the three the issue names. Its comment already argued best-effort correctly; it just
dropped
cherrtoo. Fixed with the same line. Called out rather than slipped in.Test plan
Three new tests, each mutation-tested: the fix reverted, the test red, the fix restored.
TestChannel_ReplacedBridgeIsClosedattaches two bridges with no failing ask between them —the path
drop()never takes — and reads the first client end: EOF means closed, a readtimeout means leaked.
TestServeOn_ListenFailureReachesTheStepfails the listen for a real reason (the workdiris a file), not by injecting an error, and asserts the step's stderr.
sockBasebecomes a var so the local failure is reachable at all; the reason is on it.resident.gohas no test of its own — it is the same one-line change asServeOn, and itspath is what the e2e harness exercises on every run.
go test ./...,test/lint.sh,test/dead-code.sh,test/coverage-ratchet.sh(82.0%) green.Correction to the issue: it points at
internal/agent/bridge_reconnect_test.goas alreadydriving reconnection. That file is
internal/transport/bridge_reconnect_test.go— the controlhost's side, not the agent's, so it could not have caught this.
Closes #638