Agents

Fix auth middleware

workspace/agent

You

Review the auth middleware and fix the session refresh race before the next deploy.

Agent

gpt-o3

Found a race in the refresh path: concurrent callers can invalidate a still-valid access token. Applying a serialized refresh gate and keeping credentials until a hard 401.

Plan 3 steps
  1. 01 Serialize refresh behind one in-flight promise
  2. 02 Clear session only on hard 401
  3. 03 Cover concurrent refresh in unit tests
middleware/session.ts diff
// shared refresh latch
let refreshing: Promise<Token> | null = null

if (refreshing) return refreshing
refreshing = refreshSession()
finally { refreshing = null }

You

Also check callers in the API client — a few places still clear cookies on any 401. Keep soft failures retryable.

Agent

gpt-o3

Scanned the client layer. Two helpers were treating refresh failures the same as expired sessions. Updating them to distinguish hard logout from retryable refresh errors.

Tool read_file

lib/api/client.ts

lib/api/auth-errors.ts

hooks/useSession.ts

lib/api/client.ts diff
- if (res.status === 401) clearSession()
+ if (res.status === 401 && isHardAuthFailure(res)) {
+   clearSession()
+   throw new AuthError('session_expired')
+ }
+ return retryWithRefresh(req)

You

Add a short note in the PR about why we keep the old token until refresh settles, then run the auth suite.

Agent

gpt-o3

Wrote the PR note and kicked off the auth tests. Concurrent refresh cases are covered; waiting on the suite before marking the session done.

PR note draft

Refresh is serialized behind a single in-flight promise so parallel 401s reuse one token exchange. Cookies stay until a hard auth failure, which avoids logout loops during brief refresh windows.

Soft 401s retry once after refresh; only explicit hard failures clear the session.

Tool shell
$ pnpm test auth --runInBand
 PASS  middleware/session.test.ts
 PASS  lib/api/client.test.ts
 PASS  hooks/useSession.test.ts
 ✓ 14 passed (1.8s)
middleware/session.test.ts +test
it('shares one refresh across concurrent callers', async () => {
  const a = ensureSession()
  const b = ensureSession()
  await Promise.all([a, b])
  expect(refreshSession).toHaveBeenCalledTimes(1)
})

Auth suite is green. Remaining work is a quick pass on the deploy checklist, then this session can move to Done.

You

Looks good — leave a todo for the release notes and stop there.

Agent

gpt-o3 Working

Added a release-notes todo and parked follow-ups. Summary of what shipped in this session:

Summary 4 files
  • middleware/session.ts — serialized refresh latch
  • lib/api/client.ts — hard vs soft 401 handling
  • lib/api/auth-errors.ts — shared failure helpers
  • middleware/session.test.ts — concurrent refresh coverage
Todo open
  • Mention refresh latch in release notes
  • Verify staging SSO after deploy

Agent can edit files and run tools in this workspace.