Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(auth): stop blocking OAuth callback on getThreadCount, fixing state_mismatch logins

merged#2825CedarCopilot

CedarCopilot wants to merge 1 commit into staging from fix/oauth-callback-blocking-getthreadcount

What Libra verified

  • Initial synchronization is limited to genuinely new inbound connections

    Libra ran this behavior against the change and confirmed the check detects when it breaks.

    Technical evidence
    • an outbound-only connection is left unchanged without checking synced-thread history
    • a non-outbound-only connection with an existing synced thread is left unchanged
    • a non-outbound-only connection with no synced threads starts the initial synchronization

    Changed code: auth-initial-sync.ts.

Live on prod, 0 of 2 surfaces working, less than a day leftTimeline and evidence
  1. Opened
    Sep 18, 2026, 4:09 PM
  2. Sep 18, 2026, 4:30 PM
  3. Merged
    Sep 18, 2026, 4:42 PM
  4. Live on prod
    Sep 18, 2026, 4:42 PM
  5. Observed 25 hours, 2 surfaces, 0 requests
    Sep 18, 2026, 4:42 PM
  6. Watching

    Live on prod, 0 of 2 surfaces working, less than a day left

    Sep 18, 2026, 4:42 PM
  7. Pipelines steady after this deploy
    Sep 18, 2026, 4:42 PM

Behaviors Libra is checking

Failures while checking for or starting a new connection's initial sync are caught and logged without failing or delaying the user's login.Not checked
prod
The login page explains state_mismatch as an already-used or expired sign-in link and directs the user to sign in again instead of blaming cookies, extensions, or private browsing.Not checked
prod
Google OAuth callbacks return without awaiting the synced-thread check or initial folder sync, preventing slow database work from exceeding the load balancer timeout and causing replayed callbacks to surface as state_mismatch.Not checked
prod
Outbound-only organizations skip the synced-thread lookup and do not start an inbox or sent-folder initial sync.Not checked
prod
The new-connection check determines whether any synced thread exists by fetching at most one matching row instead of selecting and deduplicating every row.Not checked
prod

Libra found 2 production surfaces on prod but could not judge any of them yet. 2 surfaces had no requests at all. Libra checks hourly for 3 days after each deploy.

Summary

  • connectionHandlerHook awaited getThreadCount() , an unbounded select+dedupe across crm_email_events/crm_events , on every Google sign-in (not just first-time connections), blocking the /api/auth/callback/google response.
  • When that query ran slow (chronic 20-118s spikes over the last 2+ weeks per Axiom), the response blew past the client's connection patience (~60s). The browser silently retried the identical callback URL, and the retry replayed an already-consumed OAuth state/code , failing as a confusing state_mismatch for real users (cedarcopilot.com, warp.co, greptile.com) on 2026-09-18.
  • Confirmed at the network layer via ALB access logs: elb_status_code=460 (client closed the connection) followed, in the same instant, by an identical-URL retry that got a real 302 state_mismatch response 242ms later.

Changes

  • Extract the initial-sync-trigger logic into auth-initial-sync.ts and call it fire-and-forget (void, never await) from connectionHandlerHook, so nothing in this path can block the callback response regardless of query latency.
  • Replace getThreadCount (unbounded select + JS Set dedupe) with hasSyncedThreads (LIMIT 1 existence check) in services/mail/threads/get.ts , its one caller only ever checked === 0.
  • Reword the state_mismatch login error copy , it blamed the browser for dropping a cookie, which is rarely the actual cause.

Full investigation (Axiom traces/logs + ALB access logs, code walkthrough, decision points) in apps/server/src/lib/BUGREPORT-oauth-callback-getthreadcount-timeout.md.

Test plan

  • New tests: apps/server/src/services/mail/threads/__tests__/get.test.ts, apps/server/src/lib/__tests__/auth-initial-sync.test.ts , both fail against the pre-fix code (missing export / unbounded query), pass after the fix.
  • apps/server/src/lib/__tests__/auth-connection-cleanup.test.ts and auth-callback-logging.test.ts (neighboring auth-hook tests) still pass.
  • pnpm --filter @zero/server run types clean.
  • Verify in prod after deploy: no further state_mismatch failures correlating with slow crm_email_events queries in Axiom.

šŸ¤– Generated with Claude Code

RetriggerConfidence Score: 4/5

The behavioral fix appears sound, but the explicit repository requirement against TypeScript assertions must be satisfied before merging.

Findings

  1. P2Ā Production Hook Remains Untested ā–¶
  2. P2Ā Tests Suppress Type Checking ā–¶
Fix with agent prompt
### Issue 1
apps/server/src/lib/__tests__/auth-initial-sync.test.ts:54
This test calls the helper with `void` itself instead of exercising `connectionHandlerHook`. It would therefore still pass if the production call at `auth.ts:289` changed back to `await`, reintroducing the slow OAuth callback without any test failure. Add coverage around the production hook, or extract its invocation into a directly testable function whose settlement can be checked while `hasSyncedThreads` remains pending.

Note: If this suggesti
Show production surfaces and changed-file mapping

Production surfaces

SurfaceRequestsErrorsp95UsersVerdict
/login0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
/api/auth/callback/google0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.

Changed files → surfaces

  • apps/mail/app/(auth)/login/login-client.tsxno production surface mapped
  • apps/server/src/lib/BUGREPORT-oauth-callback-getthreadcount-timeout.mdno production surface mapped
  • apps/server/src/lib/__tests__/auth-initial-sync.test.tsno production surface mapped
  • apps/server/src/lib/auth-initial-sync.tsno production surface mapped
  • apps/server/src/lib/auth.tsno production surface mapped
  • apps/server/src/services/mail/threads/__tests__/get.test.tsno production surface mapped
  • apps/server/src/services/mail/threads/get.tsno production surface mapped