Skip to content

fix(server): sweep dead dev session cookies at pairing - #47

Merged
asfires merged 1 commit into
mainfrom
t3code/fix-dev-session-cookie-sweep
Aug 18, 2026
Merged

asfires merged 1 commit into
mainfrom
t3code/fix-dev-session-cookie-sweep

Conversation

@asfires

@asfires asfires commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Problem

Every loopback dev server mints a unique session cookie (t3_session_<port>_<hash>) so parallel worktrees on localhost do not clobber each other. They live 30 days, are httpOnly, and nothing ever removes one. Cookies are host-scoped, not port-scoped, so every paired worktree leaves a ~600 byte cookie behind after it is pruned. Around 25-30 of them push the request header past Node's 16 KB default and Vite answers 431 Request Header Fields Too Large for every dev server on the machine.

Fix

  • Dev cookie names now encode the state directory (t3_session_<port>_<base64url(stateDir)>) instead of a one-way hash, so any dev server can tell which directory a sibling cookie belongs to.
  • When a dev server pairs a browser, it expires (Max-Age=0) every sibling dev cookie whose state directory no longer exists on disk, plus legacy-hash cookies on its own port. Deterministic: no TTL change, no port probing. Live parallel worktrees, custom --home-dir servers, and stopped-but-present worktrees keep their sessions.
  • Hosted (t3_session) and desktop (t3_session_<port>) names are unchanged. No client parses the name format.
  • The decision logic is a pure planner in auth/utils.ts; auth/http.ts only gathers FileSystem.exists results and merges the expiry cookies. Only runs when devUrl is set.
  • Internals doc note in docs/internals/environment-auth.md.

Accepted trade-off (documented in code): a page on another localhost origin could craft cookie names and observe expiry as a local path-existence oracle. Dev servers only; that page already runs on the same machine.

Verification

  • vp test run apps/server/src/auth/utils.test.ts — 9 passed (naming, decoding, sweep planner).
  • vp test run apps/server/src/server.test.ts -t "sweeps stale development session cookies when pairing" — passes (own cookie set, stale + legacy same-port expired, live sibling untouched).
  • Server typecheck and targeted lint clean.
  • reports thread HTTP and WebSocket transfer budgets in server.test.ts fails identically on unchanged origin/main in this environment; unrelated.

Not visible in the web app. To observe: pair a fresh worktree after this ships and inspect the pairing response's Set-Cookie headers in devtools. Legacy hashed cookies already in the jar are only swept when a new server lands on their port; clearing localhost cookies once after merge is the fast path.

Built by GPT-5.6 Sol via Codex CLI from a Claude Fable 5 spec; reviewed and verified by Claude Fable 5 in Claude Code.

Every loopback dev server minted a unique 30-day session cookie on localhost and nothing ever removed one, so a few dozen paired worktrees pushed request headers past Node's 16KB limit and Vite answered 431 for every dev server on the machine.

Dev cookie names now encode the state directory instead of a hash. When a dev server pairs a browser, it expires sibling dev cookies whose state directory no longer exists on disk, plus legacy-hash cookies on its own port. Live parallel worktrees are untouched; hosted and desktop cookie names are unchanged.

Built by GPT-5.6 Sol via Codex CLI from a Claude Fable 5 spec; reviewed and verified by Claude Fable 5 in Claude Code.
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L labels Aug 18, 2026
@asfires
asfires merged commit dbc57d2 into main Aug 18, 2026
6 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant