Thanks for agmsg — it's become a daily driver for multi-agent messaging here. Filing a timing-sensitive race in the Codex bridge, with a deterministic harness reproduction.
Symptom: a single wake occasionally produces two injected turns — the second an empty poll ("No new messages.") that burns a turn on the live thread. Observed in live use (on a v1.1.13-era install); the affected code path is unchanged on current main.
Mechanism: turn/completed and thread/status idle arrive independently and both funnel into the bridge's turn-end handling (#41 is why idle has to count as an end). App-server requests and notifications can arrive before an outstanding RPC response — the approval-collision test already exercises that interleaving. So:
- Turn A ends; the bridge delivers a deferred wake and sends
turn/start for turn B.
- Before B's ACK arrives, turn A's trailing end signal (an idle, or a
completed without a usable id) lands.
- The bridge attributes it to B, believes B already ended, and re-delivers the wake → a second turn from the same wake.
Reproduction: a bats harness driving the bridge against a scripted app-server reproduces the duplicate deterministically; two additional regression tests cover the legitimate orderings a fix must not break (pre-ACK completion, stale-idle attribution). On live current main (f7de22a, codex-cli 0.147.0) I could not provoke it in 13 rounds of mid-turn message bursts — the harness is what reproduces the relevant ordering reliably.
Fix shape (running locally, holding up in daily use): claim the pending wake before sending turn/start, and attribute mid-start end signals by turn id — only an id-matching completed defers the end; an idle or id-less signal landing during start is discarded, since the #41 watchdog already covers the pathological no-signal case. As defense in depth, the injected prompt reads the inbox with --quiet so an empty read cannot become a turn prompt. Patch + the three regression tests are ready; happy to PR if this direction fits.
Thanks for agmsg — it's become a daily driver for multi-agent messaging here. Filing a timing-sensitive race in the Codex bridge, with a deterministic harness reproduction.
Symptom: a single wake occasionally produces two injected turns — the second an empty poll ("No new messages.") that burns a turn on the live thread. Observed in live use (on a v1.1.13-era install); the affected code path is unchanged on current
main.Mechanism:
turn/completedandthread/statusidle arrive independently and both funnel into the bridge's turn-end handling (#41 is why idle has to count as an end). App-server requests and notifications can arrive before an outstanding RPC response — the approval-collision test already exercises that interleaving. So:turn/startfor turn B.completedwithout a usable id) lands.Reproduction: a bats harness driving the bridge against a scripted app-server reproduces the duplicate deterministically; two additional regression tests cover the legitimate orderings a fix must not break (pre-ACK completion, stale-idle attribution). On live current
main(f7de22a, codex-cli 0.147.0) I could not provoke it in 13 rounds of mid-turn message bursts — the harness is what reproduces the relevant ordering reliably.Fix shape (running locally, holding up in daily use): claim the pending wake before sending
turn/start, and attribute mid-start end signals by turn id — only an id-matchingcompleteddefers the end; an idle or id-less signal landing during start is discarded, since the #41 watchdog already covers the pathological no-signal case. As defense in depth, the injected prompt reads the inbox with--quietso an empty read cannot become a turn prompt. Patch + the three regression tests are ready; happy to PR if this direction fits.