openclaw - 💡(How to fix) Fix [Bug]: OAuth credentials duplicated per-agent cause refresh_token_reused races across agents

Official PRs (…)
ON THIS PAGE

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

When multiple agents are configured to use the same upstream provider account (same profileId across agents), OpenClaw stores full duplicate copies of the OAuth credentials in each agent's auth-profiles.json. OAuth refresh tokens for the affected provider (openai-codex) are single-use globally at the upstream: rotating one copy invalidates every other copy. The result is repeated refresh_token_reused 401s from the upstream every time a second agent attempts to use the (now stale) duplicated credentials, even though the same human-level account is fine.

The merge architecture in loadAuthProfileStoreForRuntime (in store-HF_Z-jKz.js) already supports a main store + per-agent override pattern — per-agent profiles ... over a base loaded from resolveAuthStorePath(). The bug is that per-agent stores keep populated profiles objects, and refresh writes go back to per-agent files, so the override layer holds independent (and quickly diverging) copies of credentials rather than inheriting from the main store.

Error Message

"error": { For multi-agent deployments that share an upstream account (single ChatGPT subscription, shared OAuth account), the duplication makes the system unusable as soon as more than one agent is active. The error surfaces only on the second agent's next request, not at refresh time, which makes it look like the second agent's auth is broken when in fact the cause is the first agent's refresh several minutes earlier. Diagnosis is non-obvious because every agent's local auth-profiles.json looks well-formed.

Root Cause

In store-HF_Z-jKz.js, saveAuthProfileStore(store, agentDir) writes the full merged store back to the per-agent path via buildPersistedAuthProfileStore. That function iterates store.profiles and persists every entry without distinguishing inherited-from-main vs. agent-local. So merged credentials get duplicated into the agent-local file the first time anything in the store is mutated for that agent (including unrelated usageStats updates).

On the very next refresh, the same agent will refresh against the duplicated copy, but a different agent that did not see the refresh still holds the now-rotated-out token.

Fix Action

Fix / Workaround

(1) keeps the rest of the architecture intact and is a one-spot patch. (2) is the cleaner long-term shape but touches refresh paths in openai-codex-provider-DgjQM521.js and similar.

Workaround for users on current release

saveJsonFile in json-file-1PGlTqjr.js resolves symlinks via resolveJsonWriteTarget and writes to the canonical target, so atomic renames preserve the symlink. Side effects: order, lastGood, and usageStats become shared across agents too. For most setups this is desirable (a rate-limit cooldown set by one agent prevents other agents from hitting the same wall), but if per-agent rotation state matters, this workaround is not ideal.

Code Example

{
     "error": {
       "message": "Your refresh token has already been used to generate a new access token. Please try signing in again.",
       "type": "invalid_request_error",
       "code": "refresh_token_reused"
     }
   }

---

for AGENT in <secondary-agents>; do
  rm /path/to/.openclaw/agents/$AGENT/agent/auth-profiles.json
  ln -s /path/to/.openclaw/agents/<main-agent>/agent/auth-profiles.json \
        /path/to/.openclaw/agents/$AGENT/agent/auth-profiles.json
done
RAW_BUFFERClick to expand / collapse

Summary

When multiple agents are configured to use the same upstream provider account (same profileId across agents), OpenClaw stores full duplicate copies of the OAuth credentials in each agent's auth-profiles.json. OAuth refresh tokens for the affected provider (openai-codex) are single-use globally at the upstream: rotating one copy invalidates every other copy. The result is repeated refresh_token_reused 401s from the upstream every time a second agent attempts to use the (now stale) duplicated credentials, even though the same human-level account is fine.

The merge architecture in loadAuthProfileStoreForRuntime (in store-HF_Z-jKz.js) already supports a main store + per-agent override pattern — per-agent profiles ... over a base loaded from resolveAuthStorePath(). The bug is that per-agent stores keep populated profiles objects, and refresh writes go back to per-agent files, so the override layer holds independent (and quickly diverging) copies of credentials rather than inheriting from the main store.

Reproduction

openclaw 2026.4.5. Provider openai-codex (chatgpt.com/backend-api). Two or more agents, each with agent/auth-profiles.json containing the same profile id (e.g. openai-codex:[email protected]), each with its own copy of access + refresh + accountId.

Trigger sequence:

  1. Agent A executes a turn that triggers a token refresh. Its auth-profiles.json is updated with the new access and refresh. Upstream invalidates the previous refresh.

  2. Agent B executes a turn ~minutes later. It loads its own per-agent auth-profiles.json, which still holds the old refresh token (because Agent A's update was scoped to Agent A's file).

  3. Agent B sends a request, upstream returns 401 (token expired), the client attempts refresh using its stale token, upstream returns:

    {
      "error": {
        "message": "Your refresh token has already been used to generate a new access token. Please try signing in again.",
        "type": "invalid_request_error",
        "code": "refresh_token_reused"
      }
    }
  4. [model-fallback] model fallback decision: decision=candidate_failed reason=auth next=none is logged.

  5. Agent B's lane errors with OAuth token refresh failed for openai-codex: Failed to refresh OpenAI Codex token. Please try again or re-authenticate.

Visible artifact: each agent's auth-profiles.json shows three OAuth profiles in profiles, all with non-empty access/refresh strings, all duplicating identical credentials at first (until the first refresh diverges them). Per-agent usageStats[<profile>] looks healthy (errorCount: 0, no cooldown), but the next request still 401s.

Root cause

In store-HF_Z-jKz.js, saveAuthProfileStore(store, agentDir) writes the full merged store back to the per-agent path via buildPersistedAuthProfileStore. That function iterates store.profiles and persists every entry without distinguishing inherited-from-main vs. agent-local. So merged credentials get duplicated into the agent-local file the first time anything in the store is mutated for that agent (including unrelated usageStats updates).

On the very next refresh, the same agent will refresh against the duplicated copy, but a different agent that did not see the refresh still holds the now-rotated-out token.

Proposed fix

Two non-exclusive options:

  1. Filter on save: in buildPersistedAuthProfileStore, drop any profiles[id] whose value is identical to the entry resolved from the main store. The per-agent file should only persist credentials that genuinely override main, not credentials inherited via merge.
  2. Token-refresh writes go to main: when a refresh occurs against an inherited-from-main credential, write the new tokens back to the main auth store (resolveAuthStorePath() with no agentDir) rather than the per-agent file. Other agents that resolve through merge then see the new tokens on their next load.

(1) keeps the rest of the architecture intact and is a one-spot patch. (2) is the cleaner long-term shape but touches refresh paths in openai-codex-provider-DgjQM521.js and similar.

Workaround for users on current release

Symlink per-agent auth-profiles.json files to the main (default) agent's file:

for AGENT in <secondary-agents>; do
  rm /path/to/.openclaw/agents/$AGENT/agent/auth-profiles.json
  ln -s /path/to/.openclaw/agents/<main-agent>/agent/auth-profiles.json \
        /path/to/.openclaw/agents/$AGENT/agent/auth-profiles.json
done

saveJsonFile in json-file-1PGlTqjr.js resolves symlinks via resolveJsonWriteTarget and writes to the canonical target, so atomic renames preserve the symlink. Side effects: order, lastGood, and usageStats become shared across agents too. For most setups this is desirable (a rate-limit cooldown set by one agent prevents other agents from hitting the same wall), but if per-agent rotation state matters, this workaround is not ideal.

Why this matters

For multi-agent deployments that share an upstream account (single ChatGPT subscription, shared OAuth account), the duplication makes the system unusable as soon as more than one agent is active. The error surfaces only on the second agent's next request, not at refresh time, which makes it look like the second agent's auth is broken when in fact the cause is the first agent's refresh several minutes earlier. Diagnosis is non-obvious because every agent's local auth-profiles.json looks well-formed.

Related

Filed alongside openclaw/openclaw#84457 (cooldown race wiping just-set failures). Both bugs together silently disable rotation: the first prevents cooldowns from sticking, the second causes auth to fail on the very profile that just succeeded elsewhere.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix [Bug]: OAuth credentials duplicated per-agent cause refresh_token_reused races across agents