A Discord turn that times out waiting for the execution slot tells the member nothing #132

Closed
opened 2026-08-12 02:59:40 +00:00 by coilyco-ops · 0 comments
Member

Turns are serialized on a single slot. runSerialized waits up to QueueTimeout (30s by default, internal/community/config.go:33) and on expiry records the admission outcome and returns an error.

That error reaches onMessage, which marks the span and logs discord.turn.failed and sends the member nothing (internal/community/agent.go:480-483 at 70e17e3). The member mentioned Echo and receives silence, with no reply and no error, having waited half a minute.

Why this is inconsistent rather than deliberate

Every other refusal path notifies:

  • A rate-limit denial replies through onDenied, throttled to once per notify window so a flooder gains no amplifier.
  • Exceeding MaxPending at admit time is also an admissionQueue outcome and is notified, through the same path.

So the identical outcome is reported when the queue is full at admission and silent when the wait expires afterwards. Nothing suggests that was intended.

The typing indicator does not cover it either. It starts when the turn runs, not when it queues (agent.go:538), precisely because it would expire before the reply. A queued turn shows nothing at all.

Fix

Reply on queue timeout rather than returning silently. turnIO.Reply is already polymorphic, so the same call serves every ingress: Discord sends a message, and HTTP and MCP populate the reply their error response already carries but currently leaves empty.

Discord shares the throttle the pending-cap denial uses, keyed on the same summon context so one guild's saturation does not mute another's notice. A synchronous caller is never throttled, because its reply reaches only itself and there is no channel to flood.

Split out of coilyco-gaming/sirens-echo#111, which found it. That issue remains about the progress element.

Turns are serialized on a single slot. `runSerialized` waits up to `QueueTimeout` (30s by default, `internal/community/config.go:33`) and on expiry records the admission outcome and returns an error. That error reaches `onMessage`, which marks the span and logs `discord.turn.failed` and **sends the member nothing** (`internal/community/agent.go:480-483` at `70e17e3`). The member mentioned Echo and receives silence, with no reply and no error, having waited half a minute. ## Why this is inconsistent rather than deliberate Every other refusal path notifies: * A rate-limit denial replies through `onDenied`, throttled to once per notify window so a flooder gains no amplifier. * Exceeding `MaxPending` at admit time is also an `admissionQueue` outcome and **is** notified, through the same path. So the identical outcome is reported when the queue is full at admission and silent when the wait expires afterwards. Nothing suggests that was intended. The typing indicator does not cover it either. It starts when the turn runs, not when it queues (`agent.go:538`), precisely because it would expire before the reply. A queued turn shows nothing at all. ## Fix Reply on queue timeout rather than returning silently. `turnIO.Reply` is already polymorphic, so the same call serves every ingress: Discord sends a message, and HTTP and MCP populate the reply their error response already carries but currently leaves empty. Discord shares the throttle the pending-cap denial uses, keyed on the same summon context so one guild's saturation does not mute another's notice. A synchronous caller is never throttled, because its reply reaches only itself and there is no channel to flood. Split out of coilyco-gaming/sirens-echo#111, which found it. That issue remains about the progress element.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo#132
No description provided.