Two response interceptors were stomping on each other:
1. api.ts (this file, registered at module import) — on 401, set
original._retry = true, removed access_token from localStorage,
and rejected. The comment claimed it relied on a "scheduled
refresh in AuthContext" that does not exist in the codebase.
2. AuthContext useEffect — proper refresh: POST /auth/refresh, swap
both tokens, retry the original request.
Axios runs response interceptors in registration order, so api.ts ran
first and pre-emptively burned the _retry flag + access_token before
AuthContext could see the 401. Result: every expired-token request
forced a re-login instead of a silent refresh.
Drop api.ts's response interceptor entirely. AuthContext owns the
refresh dance; the request interceptor here just attaches the bearer.
Companion bump in .env (gitignored): ACCESS_TOKEN_EXPIRE_MINUTES=10080
(7 days), REFRESH_TOKEN_EXPIRE_DAYS=365 — homelab posture, fewer
refresh round-trips per session even when the silent refresh works.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>