Skip to content

Hedge stalled Firestore message writes from Kick webhooks - #1755

Merged
kevmo314 merged 3 commits into
mainfrom
claude/eager-dijkstra-hdrup8-kick
Nov 1, 2026
Merged

kevmo314 merged 3 commits into
mainfrom
claude/eager-dijkstra-hdrup8-kick

Conversation

@kevmo314

@kevmo314 kevmo314 commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Stacked on #1754, which adds chatmessage.Create. This PR's own change is the last commit. Once #1754 merges and its branch is deleted, GitHub retargets this PR to main.

Problem

Error group CMPH0OLm48j3xgE:

kick webhook create message failed: rpc error: code = DeadlineExceeded desc = context deadline exceeded
  webhook.(*KickHandler).fail  webhook/kick.go:422   (type=chat.message.sent, request_canceled=true)

The Kick handler makes one CreateMessage attempt inside its 10s persistence budget. That budget is already detached from the request, so it survives Kick hanging up. A single stalled RPC used all 10s, the handler answered 500, and Kick had already given up (request_canceled: true).

Fix

Message documents are now written through chatmessage.Create from #1754. If an attempt stalls for 2s, or fails transiently, Create starts another one beside it, up to 3 attempts, and the first to land wins. This is safe because a message Create is idempotent: an existing document counts as written.

The metrics outcome comes from Create's duplicate result, so Kick redeliveries still count as Duplicate. A write that lands after hedging counts as Created. The 10s budget, its detachment from request cancellation, and the MergeChannel path are unchanged.

Tests

  • TestKickStalledMessageWriteIsHedged reproduces the production failure: the first message write stalls and a second would succeed. Before this change it fails with status=500 attempts=1 after 10s. Now the handler returns 200 after about 2s and stores the message.
  • The existing Kick tests still pass: fixtures, redelivery dedupe, surviving request cancellation, and the persistence budget bounds. go test -race -count=2 ./internal/webhook passes. go test ./... passes except the internal/youtube live tests, which need youtube.com (blocked in the sandbox where this was developed).

Generated by Claude Code

A Twitch chat message was dropped with "failed to write message to
firestore: context deadline exceeded". createMessage retried transient
failures within a 15s budget but had no per-attempt deadline, so one
Create RPC that stalled consumed the whole budget and no retry ran.

chatmessage.Create runs a message document Create and, since Creates are
idempotent (an existing document counts as written), starts another
attempt beside a pending one after 2s, or shortly after a transient
failure, up to three attempts. The first to land wins; duplicate is
reported only when a single attempt found the document already there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EzdqAWrbTMdtJutmK41coy
A Kick chat.message.sent delivery failed with a 500 after its message
Create returned DeadlineExceeded: the handler made a single attempt in
its 10s persistence budget, so one stalled RPC consumed all of it.

Message documents are now written through chatmessage.Create, which
starts another attempt beside a stalled or transiently failed one and
takes the first to land. Redeliveries still count as duplicates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EzdqAWrbTMdtJutmK41coy
Base automatically changed from claude/eager-dijkstra-hdrup8 to main November 1, 2026 12:50
@kevmo314
kevmo314 merged commit 098392c into main Nov 1, 2026
14 of 15 checks passed
@kevmo314
kevmo314 deleted the claude/eager-dijkstra-hdrup8-kick branch November 1, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants