fix: do not block readLoop on a departed error consumer - #9
Conversation
readLoop used a bare send on the buffer-1 errs channel. After handleFrame fails, runNode Close()s and stops receiving. If the slot is already full, that send parks the goroutine until exit. Publish through select on c.done, matching the frames path. Suite test: departed consumer does not hang readLoop. Signed-off-by: Sebastien Tardif <[email protected]>
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs real behavior proof before merge. Reviewed September 3, 2026, 7:57 AM ET / 11:57 UTC. ClawSweeper reviewWhat this changesThe PR makes terminal bridge-read errors stop waiting after client shutdown and adds tests for departed and active error consumers. Merge readiness⛔ Blocked before merge - 5 items remain Keep open: the proposed cancellation select is harmless, but the claimed full-error-buffer condition is not reachable in the current bridge client. The prior finding remains unresolved because both the new test and submitted terminal trace bypass or omit that condition. Priority: P2 Review scores
Verification
How this fits togetherThe clawgo CLI keeps a long-lived TCP connection to the gateway bridge. Its read loop sends received frames to the run loop and reports a terminal socket result through a one-slot error queue that drives reconnect or shutdown. flowchart TD
A[Gateway TCP socket] --> B[Bridge read loop]
B --> C[Frame queue]
C --> D[Run loop and frame handler]
B --> E[Terminal error queue]
D --> F[Reconnect or shutdown]
E --> F
Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Retain direct terminal error delivery unless a real lifecycle can produce multiple queued errors; if one exists, model that lifecycle end-to-end in the regression test before adding cancellation behavior. Do we have a high-confidence way to reproduce the issue? No. Current-source inspection shows readLoop is the sole producer of terminal errors and sends only once, while gateway error frames use the separate frame queue; the supplied run therefore cannot fill errs as claimed. Is this the best way to solve the issue? No. A close-aware send is only justified after identifying a real producer sequence that fills the queue; the current test injects that sequence directly rather than exercising the bridge lifecycle. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: not found in the target repository. Codex review notes: model internal, reasoning high; reviewed against c6e46796a1c8. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (1 earlier review cycle)
|
What Problem This Solves
clawgo runkeeps a long-lived TCP client to the gateway bridge.connectBridgestartsreadLoopin a goroutine. That loop already refuses to block forever when publishing frames: it selects onc.done. When the socket ends, it still did a bare send onc.errs.errsis a buffer-1 channel. AfterhandleFramereturns an error (for example a bridgeerrorframe),runNodecallsclient.Close()andgoto reconnectwithout receiving the trailingerrsvalue. The same thing happens onctx.Done(). If that slot is already occupied,readLoopparks on the send until the process exits.Evidence
Live
go runof the old blocking send versus the close-aware select. Buffer-1 slot already full.donealready closed (consumer left):Live
clawgo runagainst a local TCP bridge. The bridge answershello-ok, then sends{"type":"error","code":"TEST","message":"boom"}.handleFramereturns, the client closes, andrunNodereconnects instead of hanging:Bridge side accepted the reconnect:
Real behavior proof
readLoopcould block forever onc.errs <- errafterclawgo runclosed the client and stopped receiving from that channel.fix/readloop-tts-deadline, binary built from./cmd/clawgoto/tmp/clawgo-f004-bin. Local TCP bridge on127.0.0.1.hello-okthen a bridgeerrorframe. Ranclawgo runwith a saved token,-mdns=false,-tts-engine none,-chat-subscribe=false. Also rango run /tmp/clawgo-f004-send-demo.go.select on done returned in 0swhile the old send stayed blocked for 2s.clawgo runloggedframe error: bridge error: TEST boomand connected again on the same port.readLoopreturns whenClose()has closeddone, even iferrsis already full. A live consumer still receives the published error. Reconnect after a handled frame error still runs.Summary
Call chain:
main->run->runNode->connectBridge->go client.readLoop()->c.errs <- err/c.errs <- io.EOF.Consumer leave path:
handleFrameon a bridgeerrorframe returns ->client.Close()->goto reconnect, orctx.Done()->client.Close(). Neither path receives the trailingerrsvalue.Fix: publish through
sendErr, the sameselectonc.donealready used forc.frames.This has been present since
f601408(2026-01-04, 241 days).Related work:
readLoopalready usesselectonc.donefor frame publish.errssend.