PatchBenchCOMPLETE
showcase · captured run · execution disabledwall time 19.7s
Bug reportsubmitted 2026-09-26 07:35 UTC

Expired refresh token returns HTTP 500 instead of the documented 401

POST /auth/refresh with a refresh token past its expiry responds 500 internal_error. AuthService.refresh throws TokenExpiredError, which the HTTP error mapper does not handle, so clients treat an expired session as an outage and retry.

Expected
POST /auth/refresh with an expired refresh token responds 401. All other documented auth responses are unchanged.
Actual
500 {"error":"internal_error"}
Reproduction gateREPRODUCED
$ node --test --test-reporter=tap test/refresh-expired.test.ts
test/refresh-expired.test.ts
✕ expired refresh token returns 401, not 500
expected 401, received 500

Fails on untouched baseline ad6b83d for the expected reason. Frozen as sha256:039c25dc6c13 and injected unchanged into 3 worktrees.

Baselinead6b83dclean detached worktreetests 12/12typecheck ✓ passedlint ✓ passedbuild ✓ passedpre-existing failures 0Pre-existing baseline failures are recorded, not blocking. Candidates are rejected only for newly introduced failures.

Candidate bench

3 isolated worktrees from ad6b83d · identical frozen regression and verification commands · verified sequentially

Candidate A: Map TokenExpiredError in the HTTP error mapper

ELIGIBLE
.patchbench/worktrees/run-20260926073526-aab88e/a
  • Frozen regression intact — passed, hard gateintact
  • Regression test — passed, hard gatepass · 146ms
  • Test suite · new failures — passed, hard gate13/13 · 0 new
  • Typecheck — passed, hard gatepass · 1.4s
  • Lintinfo — passed, informationalpass · 1.0s
  • Build — passed, hard gatepass · 1.0s
+2 −1 · 1 fileInspect →

Candidate B: Treat any refresh failure as unauthorized

REJECTED
.patchbench/worktrees/run-20260926073526-aab88e/b
✕ Hard gate failed: Introduced new test failures (2)Every check still ran, so the record is complete.
  • Frozen regression intact — passed, hard gateintact
  • Regression test — passed, hard gatepass · 146ms
  • Test suite · new failures — failed, hard gate11/13 · 2 new
  • Typecheck — passed, hard gatepass · 1.4s
  • Lintinfo — passed, informationalpass · 1.0s
  • Build — passed, hard gatepass · 1.2s
+6 −2 · 1 fileWhy rejected? →

Candidate C: Handle expiry explicitly in the refresh route

ELIGIBLE
.patchbench/worktrees/run-20260926073526-aab88e/c
  • Frozen regression intact — passed, hard gateintact
  • Regression test — passed, hard gatepass · 144ms
  • Test suite · new failures — passed, hard gate13/13 · 0 new
  • Typecheck — passed, hard gatepass · 1.4s
  • Lintinfo — passed, informationalpass · 1.0s
  • Build — passed, hard gatepass · 1.1s
+8 −3 · 1 fileInspect →
secrets redacted · timeout 120s/cmd
$ pnpm run test
# exit=0 signal=null timedOut=false durationMs=602
--- stdout ---
TAP version 13
# Subtest: refresh for a disabled account returns 403 account_disabled
ok 1 - refresh for a disabled account returns 403 account_disabled
---
duration_ms: 1.976208
type: 'test'
...
# Subtest: refresh during a token-store outage returns 503, not an auth error
ok 2 - refresh during a token-store outage returns 503, not an auth error
---
duration_ms: 0.16625
type: 'test'
...
# Subtest: unexpected errors return an opaque 500
ok 3 - unexpected errors return an opaque 500
---
duration_ms: 0.154542
type: 'test'
...
# Subtest: login with valid credentials returns a session
ok 4 - login with valid credentials returns a session
---
duration_ms: 1.504667
type: 'test'
...
# Subtest: login with a wrong password returns 401 invalid_credentials
ok 5 - login with a wrong password returns 401 invalid_credentials
---
duration_ms: 0.717333
type: 'test'
...
# Subtest: login with missing fields returns 400 invalid_request
ok 6 - login with missing fields returns 400 invalid_request
---
duration_ms: 0.22325
type: 'test'
...
# Subtest: unknown routes return 404
ok 7 - unknown routes return 404
---
duration_ms: 0.112042
type: 'test'
...
# Subtest: refresh with a valid token rotates the session
ok 8 - refresh with a valid token rotates the session
---
duration_ms: 1.091833
type: 'test'
...
# Subtest: a rotated refresh token cannot be reused
ok 9 - a rotated refresh token cannot be reused
---
duration_ms: 0.690417
type: 'test'
...
# Subtest: unknown refresh token returns 401 invalid_token
ok 10 - unknown refresh token returns 401 invalid_token
---
duration_ms: 0.211083
type: 'test'
...
# Subtest: revoked refresh token returns 401 invalid_token
ok 11 - revoked refresh token returns 401 invalid_token
---
duration_ms: 0.122583
type: 'test'
...
# Subtest: missing refreshToken returns 400 invalid_request
ok 12 - missing refreshToken returns 400 invalid_request
---
duration_ms: 0.220292
type: 'test'
...
1..12
# tests 12
# suites 0
# pass 12
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 188.608667
--- stderr ---
$ node --test "test/**/*.test.ts"