anomalyco / anomalyco/opencode

Concurrent OAuth refresh from multiple processes kills rotating refresh-token series

Open
#49,439 1 comment 0 reactions 1 assignee View on GitHub

@jlongster is already working on this.

Since Sep 17, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

What happens

When several opencode processes share one mcp-auth.json, each process
refreshes OAuth tokens independently on 401. There is no cross-process
single-flight for the refresh transaction (EffectFlock in
packages/opencode/src/mcp/auth.ts only serializes file read/write, not the
refresh itself).

Against an IdP that rotates refresh tokens and revokes the whole family on
reuse, a single overlap is fatal:

  1. Process A and B both read RT1 from the shared file, both POST it to /token.
  2. The winner gets RT2. The loser gets invalid_grant (reuse detected) and the
    IdP kills the entire series.
  3. The SDK (auth() in @modelcontextprotocol/sdk@1.29.0 src/client/auth.ts) then calls invalidateCredentials('tokens'), wiping stored tokens. The server stays needs_auth` until the user re-authenticates
    manually.

With 7 processes and 300s access tokens, refreshes happen every few minutes
around the clock, so one overlap per day is enough to break the setup
permanently. Observed: 27h of continuous needs_auth, mcp-auth.json left
with only clientInfo + codeVerifier + oauthState.

Related: no retry

Failed tool calls are not retried (withClient in
packages/opencode/src/mcp/index.ts falls back to undefined; connect maps
UnauthorizedError to needs_auth + toast). So the losing call is silently
dropped even when a retry a second later would succeed.

Ask

  • Single-flight refresh across processes sharing one auth file (or at least
    re-read + retry once after a refresh race), and/or
  • Don't wipe stored tokens on a transient refresh failure; keep them so the
    next attempt can use the winner's rotated pair.

Env

opencode 1.18.30, @modelcontextprotocol/sdk 1.29.0, Windows, remote MCP
(StreamableHTTP) + self-hosted OAuth IdP with rotation + reuse detection.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.