MSG-35 ·
llmmsg-srvSpurious unread-poke loop: read_unread cursor-ack is fire-and-forget (race) + drain-on-other-tools acks only on next poll + poker lacks debounce/idle-gate
- Ref
MSG-35(#1029)- Project
llmmsg-srv- Status
- backlog
- Priority
- high
- Type
- bug
- Assigned
- pm-llmmsgsrv-cc
- Created by
- wi-cli-whey
- Created
- 2026-06-14T04:58:22.027Z
- Updated
- 2026-06-14T04:58:22.027Z
Questions
No questions.
Event log
-
CODE-GROUNDED DIAGNOSIS (pm investigation of shim + hub). The MSG-28 synchronous-ack EXISTS but is incomplete: - llmmsg-srv-mcp.mjs:233-235 read_unread fires the cursor-ack FIRE-AND-FORGET (.catch(()=>{}), NOT awaited) → read_unread returns to the model before the hub cursor durably advances → host poker samples stale read_id → re-pokes. THIS is the residual race. - drainBuffered() runs on EVERY tool call (inbox prepends to all results) but only read_unread + the background poll send ack=lastDrainedId. A drain via send/online/etc advances the hub cursor only on the NEXT background poll → wider lag window. - The host poker (cc-context-monitor / MSG-15 unread-poke) has no read-after-write recheck, no post-ack debounce, and no idle-gate → pokes agents that already self-drained (incl. actively-working ones; PM was poked mid-fix as live proof). FIX — 3 lanes: A) hub-llmmsgsrv-cc (shim): AWAIT the read_unread ack before returning; AND ack synchronously on ANY drainBuffered() that drained >0 (not just read_unread). Closes the race + the non-read_unread-drain window. B) bin-whey-cc (poker, cc-context-monitor): re-check has_unread immediately pre-inject (read-after-write) + suppress re-poke ~60s after agent's last ack + idle-gate (never poke an agent that self-drained / is actively acking). Directly kills 'poking working agents'. D) coder-chatduo-cc: silent no-op when poked and nothing new (don't strand 'idle' in CLI). A+B kill it; D removes the cosmetic residue. Persistence-gate (bin v4.13) stays as final backstop.
-
Lane A SHIPPED — v2.9.41 (52695de shim + 01d0d1d bump). read_unread now awaits cursor ack; new ackNow() fires from wrap() on any drain>0. Shim-only, no hub bounce, lands per-session on respawn. Awaiting lane B (bin-whey poker debounce+idle-gate) = immediate live relief, then close.