Skip to content

Fix: Live Ops events stuck at 0 on cold SSE Lambda - #39

Merged
yatesjalex merged 1 commit into
mainfrom
yatesclaude/stoic-haibt-053365
Apr 23, 2026
Merged

Fix: Live Ops events stuck at 0 on cold SSE Lambda#39
yatesjalex merged 1 commit into
mainfrom
yatesclaude/stoic-haibt-053365

Conversation

@yatesjalex

@yatesjalex yatesjalex commented Apr 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • On Vercel, the /api/events/activity SSE endpoint can cold-start on a Lambda that has never handled a tRPC call. The activity store + event log were seeded only as a top-level side effect of importing src/server/routers/deals.ts, so on those cold Lambdas getEventsSince(0) returned [], the initial SSE snapshot shipped replayed_count: 0, and the Live Ops DockBar counter stayed stuck at "0 events". The tRPC polling fallback filled the Events tab eventually, but the DockBar (SSE-only) never advanced.
  • Extracted the activity store, ensureSeeded, and projection helpers (reprojectOffer, getOfferSnapshot) into src/server/activity/state.ts. The SSE GET handler now calls ensureSeeded() explicitly; the call is idempotent (guarded by state.seeded), so warm instances pay nothing.
  • deals.ts re-exports the helpers external callers (webhooks/process, ai/investigate, ai/tools, tests) already depend on — no caller churn.

Test plan

  • pnpm typecheck clean
  • pnpm test — 327/327 pass, including a new app/api/events/activity/route.test.ts that simulates a cold SSE Lambda by wiping globalThis before calling GET and asserts replayed_count > 0 + an event: activity frame follows. This test fails on main and passes here.
  • pnpm exec playwright test — 9/9 pass (includes the existing Live Ops console spec and the SSE-resume spec).
  • Manual smoke: fresh browser session → landing page → click "Open My Activity" → DockBar shows "5 events".

🤖 Generated with Claude Code


Note

Medium Risk
Touches server-side in-memory seeding and the SSE GET /api/events/activity path; incorrect seeding/idempotency could cause missing or duplicated replay events across Lambda instances.

Overview
Fixes a cold-start issue where the /api/events/activity SSE Lambda could start without any seeded activity events, causing the initial snapshot to report replayed_count: 0 and leaving clients stuck on an empty stream.

Seeding/projection logic is extracted into a new src/server/activity/state.ts module (store management, ensureSeeded, reset, and projection helpers), deals.ts is updated to use/re-export those helpers, and the SSE handler now calls ensureSeeded() explicitly. Adds a vitest that simulates a cold SSE Lambda by clearing globalThis state and asserting that seed events are replayed.

Reviewed by Cursor Bugbot for commit b8474db. Bugbot is set up for automated code reviews on this repo. Configure here.

Symptom: navigating to /earn/activity sometimes left the Live Ops DockBar
stuck at "0 events" — the SSE snapshot arrived with replayed_count: 0 and
no activity frames ever followed, so the counter never advanced.

Root cause: on Vercel the SSE endpoint, tRPC handler, and webhook each run
as separate Lambda functions with per-process globalThis state. The
activity store and event log were seeded only as a top-level side effect
of importing src/server/routers/deals.ts. The SSE route never imports
that module, so when routing luck put the SSE connection on a cold Lambda
that had never handled a tRPC call, getEventsSince(0) returned [] and
latest_seq was 0. Polling eventually filled the Events tab via the tRPC
Lambda, but the DockBar counter (wired to SSE deltas only) stayed at zero.

Fix: extract the activity store + seed + projection into
src/server/activity/state.ts and call ensureSeeded() inside the SSE GET
handler. ensureSeeded is idempotent (guarded by state.seeded), so double
seeding is a no-op on warm instances. deals.ts now imports from the new
module and re-exports the helpers external callers already depend on, so
no caller churn.

Verified: 327/327 unit tests (including the new
app/api/events/activity/route.test.ts which reproduces the cold-Lambda
case by wiping globalThis before calling GET), pnpm typecheck clean, 9/9
Playwright specs green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
super Ready Ready Preview, Comment Apr 23, 2026 5:52am

@yatesjalex
yatesjalex merged commit 2315065 into main Apr 23, 2026
7 checks passed
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.

1 participant