Problem
The Hermes Runner's Prompt goroutine reads r.scanner.Scan() without holding r.mu, while Start also uses the same scanner during the init handshake. Two overlapping prompts could race on the same scanner and deliver a response to the wrong consumer (matched by ID, but only after stealing lines from the other prompt). r.running is also touched lock-free.
go test -race would flag this — it isn't run on the package.
Where
internal/hermes/runner.go:268 — Prompt reading r.scanner.Scan()
internal/hermes/runner.go:115-181 — Start handshake on same scanner
internal/hermes/runner.go — r.running writes/reads without atomic or mutex
Fix
- Move scanner reads into a single goroutine started in
Start (a dispatch loop)
- Maintain a
map[int64]chan response under r.mu keyed by request ID
- Each
Prompt allocates an ID, registers a channel, sends the request, and reads its own channel
- Convert
r.running to atomic.Bool
Acceptance criteria
go test -race ./internal/hermes/... clean
- New test: two concurrent
Prompt calls on the same Runner each get their own response, no cross-talk
- Bridge process death is observed by the dispatch loop and propagated to all waiting
Prompt channels via context cancel
Labels
bug, priority: critical (use defaults available in clanker-cli)
Problem
The Hermes Runner's
Promptgoroutine readsr.scanner.Scan()without holdingr.mu, whileStartalso uses the same scanner during the init handshake. Two overlapping prompts could race on the same scanner and deliver a response to the wrong consumer (matched by ID, but only after stealing lines from the other prompt).r.runningis also touched lock-free.go test -racewould flag this — it isn't run on the package.Where
internal/hermes/runner.go:268—Promptreadingr.scanner.Scan()internal/hermes/runner.go:115-181—Starthandshake on same scannerinternal/hermes/runner.go—r.runningwrites/reads without atomic or mutexFix
Start(a dispatch loop)map[int64]chan responseunderr.mukeyed by request IDPromptallocates an ID, registers a channel, sends the request, and reads its own channelr.runningtoatomic.BoolAcceptance criteria
go test -race ./internal/hermes/...cleanPromptcalls on the same Runner each get their own response, no cross-talkPromptchannels via context cancelLabels
bug, priority: critical (use defaults available in clanker-cli)