Skip to content

limits: bound the serial Codex token refresh so single-flight cannot hang every caller #144

Description

@pitimon

What

getUsageLimits() can hang forever before it reaches the provider timeout layer. When Codex auth is stale, src/lib/usage-limits.js awaits refreshCodexTokens() in the serial prelude and passes the raw fetchImpl. src/lib/codex-token-refresh.js:20-27 performs that request without an AbortSignal or timeout.

PR #143 adds single-flight coalescing after the completed-cache check. That improves fan-out efficiency, but it changes the blast radius of this existing hang: every later quota request joins the same unsettled sweep instead of starting an independent attempt.

Evidence

Impact

A stalled Codex OAuth endpoint can make the usage-limits endpoint remain pending indefinitely. With #143, one stalled refresh holds the single-flight slot and all dashboard tabs, scheduled revalidations, and manual quota refreshes join it.

Suggested shape

  • Give refreshCodexTokens a bounded timeout/abort path, preferably using the same configured provider timeout contract rather than a second unrelated constant.
  • Ensure the fetch is actually aborted where supported, not merely raced while the underlying request continues.
  • Preserve existing refresh error semantics: best-effort failure falls back to the current access token; REFRESH_TOKEN_EXPIRED remains actionable.

Definition of done

  • A never-settling Codex refresh fetch settles within the configured bound.
  • Joined callers receive a bounded result rather than hanging indefinitely.
  • After timeout, the single-flight slot releases and a later request can start a new sweep.
  • Existing 401/expired-token and successful-refresh behavior remains covered.
  • Test includes a hanging fetchImpl control that fails without the fix.
  • npm run ci:local passes.

Scope

Do not redesign all provider scheduling in this issue. This is the unbounded serial Codex refresh prelude only.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions