feat: add global auth-failure circuit breaker to coordinate polling on 401 - #720
Conversation
…ling on 401 Intercept 401s at the single fetch chokepoint (handleFetchRequest) and route them through a framework-agnostic circuit breaker that coordinates the whole app's reaction: pause all polling, run a single reauth, then resume on success or stop and surface an error on failure. - Add src/api/auth-circuit-breaker.ts: closed/reauthing/open state machine with subscribe/report401/configure/reset. Dedupes concurrent 401s into one reauth. - handleFetchRequest reports every 401 to the breaker. - use-reauth.tsx: new useAuthCircuitBreaker hook wires the real reauth call and error surfacing (redirect/suppress/dispatch) into the breaker; mounted in App. - use-line-polling, use-heartbeat: gate each loop on the breaker, waiting for resume (counters reset) or stopping when tripped. - use-fetch-production-list: skip fetches while the breaker is non-active and defer 401 handling to the breaker instead of an independent reauth. - use-websocket-reconnect: hold off reconnecting while the breaker is non-active. - Tests for the breaker, heartbeat pause/resume/stop, and updated fetch-list 401 expectations. Resolves #597 Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
code-reviewer verdict: NEEDS CHANGES (automated self-review; recorded as a marker because GitHub blocks state-bearing self-review when author and reviewer are the same account). Blocking
Warnings
Suggestions
The core dedup guarantee (exactly one coordinated reauth under concurrent 401s) is correct, there's no import cycle, and subscription cleanup is sound — the issue is specifically the terminal/silent open state. |
…ing forever Address code-review (NEEDS CHANGES) on the global auth-failure circuit breaker. Blocking: the `open` state was terminal (only reset() on unmount exited it) and silent for suppressed statuses (500/404/405). A transient 500 (OSC token refresh) or a stray pre-config 401 permanently froze ALL app-wide polling — including the active-call heartbeat — which let the manager reclaim the live SMB session and silently kill the call. The breaker is now non-terminal: on failure it schedules a half-open retry on an exponential backoff (2s..30s) and keeps retrying until reauth succeeds, at which point every polling loop resumes. The stall is also made visible — a non-fatal, auto-dismissing "reconnecting" warning is surfaced for the previously-silent suppressed cases, and cleared via an onRecover callback when a retry recovers. - use-heartbeat / use-line-polling: wait for resume whenever the breaker is not active (reauthing OR open) instead of stopping dead on open, so polling resumes once the breaker recovers. Warnings: - Defensive guard: a 401 that arrives before configure() wires in the runner no longer trips the breaker open with a swallowed error — it is remembered and replayed once configure() runs, keeping polling alive meanwhile. - Added test coverage: half-open recovery, gate() pause/resume in use-line-polling, use-websocket-reconnect breaker integration, use-fetch-production-list pause/resume, and heartbeat resume-after-open. Suggestions: - Guard the reauth failure branch with a reauthing-state check (mirrors the success branch) so a reauth in flight when reset() ran cannot re-open the breaker. - Note in use-heartbeat that the breaker is now the primary 401 coordinator. Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
code-reviewer verdict: LGTM (automated self-review; recorded as a marker because GitHub blocks state-bearing self-review when author and reviewer are the same account). The prior blocking defect — the circuit breaker's Non-blocking follow-ups for later (not gating merge): persistent 404/405 reauth-impossible backends keep polling paused (consider treating definitive 404/405 as resume-and-stay-closed); add a DEV guard mirroring |
Closes #597
Summary
src/api/auth-circuit-breaker.tsimplementing a three-state machine (closed → reauthing → closed on success / open on failure).handle-fetch-request.ts, a single chokepoint that reports to the breaker: the first 401 trips it to reauthing while concurrent 401s are deduplicated so exactly one coordinated reauth runs.use-reauth.tsx, a hook mounted inApp.tsx, to configure the breaker with the reauth runner and error handling.use-line-polling.ts,use-heartbeat.ts,use-fetch-production-list.ts, anduse-websocket-reconnect.tspause while reauthing, resume with reset failure counters on success, and stop when the breaker trips open.Test plan
npm testin both repos)npm run typecheck)npm run lint)auth-circuit-breaker.test.tscovers the 10 state-machine cases🤖 Generated with Claude Code