fix: stop previous TTS queue on reconnect - #7
Conversation
Default clawgo run rebuilds the TTS queue after every hello-ok. The loop ranges a never-closed channel, so each bridge drop leaks a goroutine. Stop the previous queue before starting the next one, and stop the last queue when run returns. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: blocked before merge. Reviewed September 4, 2026, 7:55 AM ET / 11:55 UTC. ClawSweeper reviewWhat this changesThe PR replaces the text-to-speech queue after each bridge reconnect, adds queue-stop signaling, and tests that an idle prior worker exits. Merge readiness⛔ Blocked before merge - 3 items remain Keep open: the PR fixes idle queue workers after reconnect, but it still cannot stop a worker already running system TTS, so the old goroutine and child process can remain active until speech returns. Priority: P2 Review scores
Verification
How this fits togetherClawgo is a headless node that reconnects to an OpenClaw gateway bridge and can speak subscribed chat responses through a local system TTS command. Bridge reconnects create chat subscriptions and TTS workers, which then launch the local speech process. flowchart LR
A[Gateway bridge] --> B[Reconnect loop]
B --> C[Chat subscription]
C --> D[TTS queue worker]
D --> E[System speech process]
B --> F[Replace prior queue]
F --> D
Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Copy recommended automerge instructionTechnical reviewBest possible solution: Make queue shutdown cancel in-flight system TTS as well as queued work, then prove a blocking speech command and its child exit on reconnect without changing normal TTS configuration. Do we have a high-confidence way to reproduce the issue? Yes for the idle-worker defect: the supplied dropping-bridge trace demonstrates it, and current-main source retains the same queue construction. The remaining active-speech gap is directly established by the synchronous, uncancelable system TTS path. Is this the best way to solve the issue? No: closing the queue signal is sufficient only before a worker enters Speak; queue-owned cancellation must reach the active system speech process. 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:
EvidenceAcceptance criteria:
What I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (11 earlier review cycles; latest 8 shown)
|
What Problem This Solves
Default
clawgo run(-chat-subscribe true,-tts-engine system) builds a TTS queue after everyhello-ok. The queue loop ranges a channel that is never closed. A bridge drop reconnects, starts a second queue, and leaves the first goroutine ranging forever.Each later drop adds another leaked loop. On a flaky Raspberry Pi or Linux link this grows without bound until
clawgo runis killed.This PR stops the previous queue before starting the next one after reconnect, and stops the last queue when
runreturns. Introduced inf601408(2026-01-04) and present for 237 days. Related reconnect work: #5, #6.Evidence
Before (binary from
upstream/mainat5f1b9d9). Fake bridge accepts, sendshello-ok, then drops. Two drops, third session, then SIGQUIT. ThreeTTSQueue.loopgoroutines still ranging:After (this patch). Same fake bridge and two drops. One loop remains (the current session):
Reconnect helper without
Stopleft the old loop ranging. WithStopit exited:Real behavior proof
Behavior or issue addressed: Bridge reconnect no longer leaves the previous TTS queue goroutine ranging a never-closed channel.
Real environment tested: macOS 26.6.2 (Darwin 25.6.0 arm64), go1.27.0, clawgo built from this branch at
/tmp/oc-pr-clawgo-F003. Compared against a binary built fromupstream/main(5f1b9d9).Exact steps or command run after this patch: Started a local TCP bridge that replies
hello-okand then drops the socket. Wrote a state file with a token so pairing is skipped. Ranclawgo run -bridge 127.0.0.1:$PORT -mdns=false -tts-engine system -tts-system-command /usr/bin/true. After three hello-ok sessions (two reconnects), sent SIGQUIT and countedmain.(*TTSQueue).loopstacks. Also rango test ./cmd/clawgo -run TestReplaceTTSQueueStopsPreviousLoop -count=1 -timeout 15s -vbefore and after callingStopon the previous queue.Evidence after fix: terminal output from the patched binary:
The unfixed binary after the same two reconnects:
Observed result after fix: After two bridge drops, the patched
clawgo runhad oneTTSQueue.loopgoroutine. The unfixed binary had three, one per hello-ok.What was not tested: Live TTS against a real OpenClaw gateway and
espeak-ngaudio output. The leak is the ranging loop, not the speak binary./usr/bin/truewas used as-tts-system-commandsoLookPathsucceeds on this host.Command: live
clawgo runagainst a dropping TCP bridge, then SIGQUIT. Alsogo test ./cmd/clawgo -run TestReplaceTTSQueueStopsPreviousLoop -count=1 -timeout 15s -v.Observed: unfixed binary, 3
TTSQueue.loopstacks after two reconnects. Patched binary, 1 stack. Red step failed at 2.00s (previous TTS queue still ranging after reconnect). Green step passed at 0.00s.Expected: each reconnect stops the previous queue so only the current session loop remains.
Time: 11:29:13 PDT
Date: 2026-08-29
Environment: macOS 26.6.2, Darwin 25.6.0 arm64, go1.27.0 darwin/arm64