openclaw - ✅(Solved) Fix resolveCliAuthEpoch invalidates every live CLI session when local credential source-of-storage flips, even though identity is unchanged [2 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

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…
GitHub stats
openclaw/openclaw#80178Fetched 2026-05-11 03:17:58
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
2
Timeline (top)
referenced ×3cross-referenced ×2commented ×1

resolveCliAuthEpoch (in the runtime prepare bundle) hashes both the local CLI credential fingerprint AND the auth profile credential. The local fingerprint contributes a local:<provider>:<hash> part if and only if the local credential reader returns non-null. When the underlying credential source's presence flips (e.g. ~/.claude/.credentials.json is newly written by claude or rotated in place, while the keychain item is unchanged), the local fingerprint flips from undefined ↔ a real hash, and the resulting epoch hash flips wholesale — even though the OAuth identity (clientId / email / accountId) has not changed.

Because every binding's stored authEpoch no longer matches the current one, cli-session.ts returns invalidatedReason: "auth-epoch" for every live CLI session at once. The cli-backend then closes the live sessions (reason=restart) and starts fresh execs with useResume=false reuse=invalidated:auth-epoch. To users this looks like "my session just lost all its history".

Root Cause

Because every binding's stored authEpoch no longer matches the current one, cli-session.ts returns invalidatedReason: "auth-epoch" for every live CLI session at once. The cli-backend then closes the live sessions (reason=restart) and starts fresh execs with useResume=false reuse=invalidated:auth-epoch. To users this looks like "my session just lost all its history".

Fix Action

Fix / Workaround

``` UNPATCHED file-missing : 11a47bc82f80e2bffea002ba595cce8411e2c4591b1d296bca5c9d52f0559962 UNPATCHED file-present : 5a727efeaae70e932b3d4152ec13290f88af391cc59086141860d07c9c6aa4e8 ```

Local mitigation

I'm running this as a local prepare.runtime-*.js patch (AUTH_EPOCH_STABLE_IDENTITY_v1 marker) until upstream lands a fix. Happy to open a PR if helpful.

PR fix notes

PR #80265: fix(agents): keep cli-auth-epoch stable when local credential file presence flips

Description (problem / solution / changelog)

Summary

  • Extend shouldSkipLocalCliCredentialEpoch at the prepare/backend auth-epoch policy seam to skip local credentials for identity-less OAuth profiles (no email, no accountId) when execution is prepared.
  • When a profile credential is OAuth but carries no stable account identity, local credential file presence was flipping the auth epoch hash, causing spurious reason=auth-epoch session resets for Anthropic Max plan users.
  • Both sides encode the same identity-less constant (["oauth","provider",null,null,null,null,null]), so the only observable epoch change is file existence — not an actual account change.
  • The fix keeps combined-mode behavior intact for identity-bearing profiles (those with email or accountId).

Verification

  • pnpm test src/agents/cli-runner/prepare.test.ts — 21 passed
  • pnpm test src/agents/cli-auth-epoch.test.ts — 13 passed
  • pnpm test src/agents/cli-session.test.ts — 14 passed
  • New test cases in prepare.test.ts cover identity-less skip, identity-bearing non-skip, and no-execution non-skip.
  • Regression test in cli-auth-epoch.test.ts now asserts epoch equality (fixed behavior) with skipLocalCredential: true.

Real behavior proof

Behavior addressed: Identity-less OAuth profiles no longer include local credential file presence in the auth epoch, preventing spurious session resets.

Environment tested: Node 22.x on Linux, pnpm test + node --import tsx live verification.

Steps run after the patch: Ran shouldSkipLocalCliCredentialEpoch via node --import tsx with identity-less and identity-bearing OAuth credentials.

Evidence:

identity-less OAuth with execution: true
identity-bearing OAuth with execution: false
identity-less OAuth no execution: false
profile-only mode: true

Observed result: Identity-less OAuth profiles correctly skip local credentials; identity-bearing profiles remain in combined mode; profile-only mode unchanged.

Not tested: End-to-end session reuse with live Claude CLI credentials.

Closes #80178

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/agents/cli-auth-epoch.test.ts (modified, +53/-2)
  • src/agents/cli-auth-epoch.ts (modified, +1/-1)
  • src/agents/cli-runner/prepare.test.ts (modified, +49/-0)
  • src/agents/cli-runner/prepare.ts (modified, +19/-3)

PR #80425: fix(cli-auth-epoch): exclude local credential from epoch when auth-profile is present

Description (problem / solution / changelog)

Problem

resolveCliAuthEpoch included the local CLI credential file fingerprint in the epoch hash even when an auth-profile credential was present. When the claude CLI wrote or refreshed ~/.claude/.credentials.json, the local fingerprint flipped (present→absent or vice versa), triggering a spurious epoch change that invalidated every live CLI session — even though the user's OAuth identity was unchanged.

Fix

Resolve the auth-profile credential first. If one is found, use it as the canonical identity source and exclude the local file from the epoch hash. Fall back to the local fingerprint only in the bootstrap case (auth-profile ID provided but not yet populated in the store).

Real behavior proof

Behavior or issue addressed: resolveCliAuthEpoch returns different hashes before and after ~/.claude/.credentials.json appears on disk, even when the user's OAuth identity (email/accountId) is stable and an auth-profile credential is present. Every live CLI session is invalidated spuriously on each credential file write.

Real environment tested: Node.js v22, Linux, openclaw monorepo from source with this patch applied.

Exact steps or command run after fix:

pnpm vitest run src/agents/cli-auth-epoch.test.ts
openclaw /status

Evidence after fix: The new regression test "keeps epoch stable when local credential file flips while auth-profile credential is present" simulates readClaudeCliCredentialsCached returning a credential on the first call and null on the second (file disappeared). With the fix, both resolveCliAuthEpoch calls return the same epoch hash — no spurious invalidation. The updated test "profile credential is canonical when present" verifies local token/accountId changes leave the epoch unchanged while profile identity changes shift it correctly.

Test Files  2 passed (2)
     Tests  28 passed (28)

Observed result after fix: Running openclaw /status after credential file rotation shows a stable auth label (no session re-init). 28/28 unit tests pass including two new regression tests for the exact bug scenario.

What was not tested: Keychain-backed credential stores; multi-machine state convergence.

Closes #80178.

Changed files

  • src/agents/cli-auth-epoch.test.ts (modified, +105/-12)
  • src/agents/cli-auth-epoch.ts (modified, +19/-10)
  • src/agents/model-auth-label.test.ts (modified, +30/-0)
  • src/agents/model-auth-label.ts (modified, +14/-11)
RAW_BUFFERClick to expand / collapse

Summary

resolveCliAuthEpoch (in the runtime prepare bundle) hashes both the local CLI credential fingerprint AND the auth profile credential. The local fingerprint contributes a local:<provider>:<hash> part if and only if the local credential reader returns non-null. When the underlying credential source's presence flips (e.g. ~/.claude/.credentials.json is newly written by claude or rotated in place, while the keychain item is unchanged), the local fingerprint flips from undefined ↔ a real hash, and the resulting epoch hash flips wholesale — even though the OAuth identity (clientId / email / accountId) has not changed.

Because every binding's stored authEpoch no longer matches the current one, cli-session.ts returns invalidatedReason: "auth-epoch" for every live CLI session at once. The cli-backend then closes the live sessions (reason=restart) and starts fresh execs with useResume=false reuse=invalidated:auth-epoch. To users this looks like "my session just lost all its history".

Reproduction (verified)

A standalone unit test that mocks both inputs reproduces the bug deterministically:

``` UNPATCHED file-missing : 11a47bc82f80e2bffea002ba595cce8411e2c4591b1d296bca5c9d52f0559962 UNPATCHED file-present : 5a727efeaae70e932b3d4152ec13290f88af391cc59086141860d07c9c6aa4e8 ```

Both hashes also appear verbatim in the live agents/<agent>/sessions/sessions.json — old bindings (created when the file was missing) carry 11a47bc8…, new bindings (created after the file appeared) carry 5a727efe…. They are the SAME OAuth identity ({type:"oauth", provider:"claude-cli"} with no email/clientId/accountId set in either source — Anthropic Max plan).

Real-world impact (incident, 2026-05-10 ~04:00 EDT, openclaw 2026.5.7)

A long-running main agent session was abandoned mid-conversation when ~/.claude/.credentials.json came into existence. Gateway log:

``` 07:53:16 cli session reset: provider=claude-cli reason=auth-epoch 07:53:16 cli exec: ... reuse=invalidated:auth-epoch ```

The next user message routed to a brand-new agent session with no memory of the prior 1.6MB of conversation context. The original session's JSONL was intact on disk, but the live agent handle was gone and there was no resume path back from the user's perspective.

Proposed fix

Treat the auth profile credential as the canonical identity source when present. Only contribute the local CLI fingerprint when no auth profile credential exists (the bootstrap case):

```js async function resolveCliAuthEpoch(params) { const provider = params.provider.trim(); const authProfileId = normalizeOptionalString(params.authProfileId); const parts = []; let profileCredential; if (authProfileId) { profileCredential = getAuthProfileCredential( cliAuthEpochDeps.loadAuthProfileStoreForRuntime(void 0, { readOnly: true, allowKeychainPrompt: false }), authProfileId, ); } if (params.skipLocalCredential !== true) { const localFingerprint = getLocalCliCredentialFingerprint(provider); if (localFingerprint && !profileCredential) parts.push(`local:${provider}:${localFingerprint}`); } if (profileCredential) parts.push(`profile:${authProfileId}:${hashCliAuthEpochPart(encodeAuthProfileCredential(profileCredential))}`); if (parts.length === 0) return; return hashCliAuthEpochPart(parts.join("\n")); } ```

The same unit test that reproduced the bug confirms with this change:

  • File-missing and file-present produce the same epoch (no spurious invalidation).
  • A real identity change in the auth profile (e.g. different email / accountId) still flips the epoch (real reauth still detected).
  • The bootstrap case (no auth profile yet) still uses the local fingerprint as the trigger.

Alternative shapes considered

  • Identity-tuple equivalence check between local and profile — strictly more permissive (would also no-op for keychain reauth as a different account, which we want to keep noticing).
  • Only hash the OAuth identity tuple, drop tokens entirely — the existing encodeOAuthIdentity already does this for OAuth; the bug is purely about the presence of the local source, not the value of the hash.

Local mitigation

I'm running this as a local prepare.runtime-*.js patch (AUTH_EPOCH_STABLE_IDENTITY_v1 marker) until upstream lands a fix. Happy to open a PR if helpful.

Environment

  • openclaw 2026.5.7
  • macOS 26.3.1 (arm64), node 25.9.0
  • Auth: claude-cli (Max plan), OAuth credentials present in both ~/.claude/.credentials.json and the macOS Keychain

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 - ✅(Solved) Fix resolveCliAuthEpoch invalidates every live CLI session when local credential source-of-storage flips, even though identity is unchanged [2 pull requests, 1 comments, 2 participants]