Symptom
User sends a message in a session's Telegram topic. The daemon consumes the update (seen-updates advances), sets the QUEUED reaction (user sees an ACK), forwards user_message over the session ws — and the message never reaches the session. No log line anywhere.
Reproduced live (2026-08-14, ~10:05 KST)
- Daemon healthy, freshly attached to the session's current endpoint (
ws://127.0.0.1:52715, pid 59092), same endpoint the broker index advertises (live: true, mtime strict-equal).
- Direct probe: connected to that endpoint with
SdkClient.connect(url, token) and sent a well-formed user_message frame — accepted at the socket, silently discarded by the host. No sendUserMessage failed warn, no drop diagnostic.
- The host runtime also stopped publishing outbound (turn receipts frozen), while its endpoint registration was never rotated or marked stale, and daemon-restart-triggered reconciliation never replaced it.
Root cause surfaces (src/sdk/bus/index.ts server.onInbound)
Two unconditionally silent drop gates:
if (initializedRuntime.inboundFenced) return; — inboundFenced is set during identity rotation / predecessor-terminal paths and is never reset; a fenced predecessor keeps its native server listening and its broker endpoint advertised, so the daemon keeps delivering into a blackhole indefinitely.
if (runtime?.policySuspended && notificationOrigin) { …defer control_command…; return; } — swallows user_message/reply/config_command while policy is provisional; session-control.ts has exits that leave a runtime provisional forever (e.g. reconcile attempt exhaustion at the tail of #reconcile).
Compounding: the daemon flips the Telegram QUEUED reaction immediately after transport.send, before any session-side acceptance, so the user gets a positive ACK for a dropped message.
Fixed so far
PR #4526 (11e98cf6ce) adds bounded warn diagnostics at both drop gates. Not fixed (needs design):
- successor lifecycle: a fenced runtime must either rotate its endpoint registration out of the broker index or be replaced so reconciliation can converge;
- daemon ACK should reflect session acceptance, not socket write;
#reconcile must not exit leaving policySuspended = true on a live runtime.
Related: #4527 (silent reconcile non-convergence), PR #4526 (registry brick).
Symptom
User sends a message in a session's Telegram topic. The daemon consumes the update (seen-updates advances), sets the QUEUED reaction (user sees an ACK), forwards
user_messageover the session ws — and the message never reaches the session. No log line anywhere.Reproduced live (2026-08-14, ~10:05 KST)
ws://127.0.0.1:52715, pid 59092), same endpoint the broker index advertises (live: true, mtime strict-equal).SdkClient.connect(url, token)and sent a well-formeduser_messageframe — accepted at the socket, silently discarded by the host. NosendUserMessage failedwarn, no drop diagnostic.Root cause surfaces (src/sdk/bus/index.ts
server.onInbound)Two unconditionally silent drop gates:
if (initializedRuntime.inboundFenced) return;—inboundFencedis set during identity rotation / predecessor-terminal paths and is never reset; a fenced predecessor keeps its native server listening and its broker endpoint advertised, so the daemon keeps delivering into a blackhole indefinitely.if (runtime?.policySuspended && notificationOrigin) { …defer control_command…; return; }— swallowsuser_message/reply/config_commandwhile policy is provisional;session-control.tshas exits that leave a runtime provisional forever (e.g. reconcile attempt exhaustion at the tail of#reconcile).Compounding: the daemon flips the Telegram QUEUED reaction immediately after
transport.send, before any session-side acceptance, so the user gets a positive ACK for a dropped message.Fixed so far
PR #4526 (
11e98cf6ce) adds bounded warn diagnostics at both drop gates. Not fixed (needs design):#reconcilemust not exit leavingpolicySuspended = trueon a live runtime.Related: #4527 (silent reconcile non-convergence), PR #4526 (registry brick).