Skip to content

Fix qc throttling and 'session expired': Bearer auth for events, no refresh storm (0.5.1, qc 0.4.2) - #284

Merged
ralyodio merged 1 commit into
masterfrom
fix/qc-throttle-refresh
Oct 6, 2026
Merged

ralyodio merged 1 commit into
masterfrom
fix/qc-throttle-refresh

Conversation

@ralyodio

@ralyodio ralyodio commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Anthony: "qc gets errors loading, once got throttled and another said session has expired".

Root cause: createSupabaseServerClientWithToken called setSession({ access_token, refresh_token: '' }). auth-js 2.74 throws AuthSessionMissingError for an empty refresh token, so every Bearer-authenticated request through authenticateRequest returned 401 (29 [SSE] Authentication failed in the live logs). Browsers use cookies, so only qc, MCP and API clients were affected.

qc then refreshed and reconnected /api/events immediately, with no backoff. GoTrue logged token_refreshed/token_revoked for Anthony's account several times within 60 ms. Each refresh is an /api request, which pushed past the 60/min per-IP limit (the throttle), and the rotation eventually killed the session ("session has expired").

Fixes

  • The Bearer client carries the token in the Authorization header (RLS still sees the user), and the route calls getUser(token).
  • qc: one refresh per rejection, and the events stream stops with "run qc login" if even a fresh token is refused. Parallel 401s share a single refresh. A 401 for an already-replaced token retries without refreshing.
  • qc waits out 429s using Retry-After, which the middleware now sends (10 s).
  • The TUI no longer loads 40 chats at startup just to join their live rooms; /api/events joins all of the user's conversations on connect.
  • sseManager.removeConnection only removes the user from rooms when their last connection closes. Before, qc reconnecting silenced the same user's open browser tab.

Tests: 666/666, with new tests for qc 429 handling, single refresh and stopping the events loop, the token getUser call, and SSE rooms. next build passes.

🤖 Generated with Claude Code

…efresh storm (0.5.1, qc 0.4.2)

- authenticateRequest's token client called setSession() with an empty
  refresh token, which auth-js rejects, so every Bearer request through it
  (qc's /api/events) was a 401. Use a client carrying the Authorization
  header and getUser(token).
- qc reconnected /api/events on 401 straight after a refresh with no
  backoff, rotating refresh tokens in a tight loop (GoTrue logged several
  per 60ms) until the session died. Now one refresh per rejection, then stop.
  Parallel 401s share one refresh; a stale-token 401 just retries.
- qc waits out 429s (Retry-After, which the middleware now sends).
- The TUI no longer loads 40 chats on start to join their live rooms: the
  events stream joins all of the user's conversations on connect.
- Closing one SSE connection no longer drops the user from every room while
  another of their connections is open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

13 finding(s)

MEDIUM: 9 | LOW: 4

Severity Rule Location
MEDIUM redos-nested-quantifier src/app/api/profile/update/route.js:73
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.jsx:38
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.jsx:66
MEDIUM js-unescaped-html-sink src/app/faq/page.jsx:57
MEDIUM js-unescaped-html-sink src/app/layout.jsx:137
MEDIUM js-unescaped-html-sink src/app/layout.jsx:141
MEDIUM js-unescaped-html-sink src/app/page.jsx:47
MEDIUM redos-nested-quantifier src/lib/auth/dns-name.js:88
MEDIUM js-unescaped-html-sink src/lib/components/chat/MessageItem.jsx:121
LOW secret-generic-credential src/app/api/auth/register-anon/route.test.js:32
LOW secret-jwt tests/debug-sms.js:10
LOW secret-generic-credential tests/private-key-import-export.test.js:252
LOW secret-generic-credential tests/private-key-import-export.test.js:264

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 8bbb787 into master Oct 6, 2026
11 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