Skip to content

Fix Claude same-dir account-switch history mis-attribution (#1886)#1903

Merged
steipete merged 15 commits into
steipete:mainfrom
ss251:fix/claude-account-switch-same-dir-history
Jul 5, 2026
Merged

Fix Claude same-dir account-switch history mis-attribution (#1886)#1903
steipete merged 15 commits into
steipete:mainfrom
ss251:fix/claude-account-switch-same-dir-history

Conversation

@ss251

@ss251 ss251 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Addresses the history-isolation portion of #1886.

Claude Code can update the shared Keychain item and ~/.claude.json during /login while ~/.claude/.credentials.json remains stale. A background refresh could then attribute a successful OAuth sample to the wrong account-history lifecycle.

Final design

  • Derives an opaque history owner from the exact OAuth credential used for the successful fetch; raw tokens are never persisted.
  • Brackets the fetch with prompt-free credential/account observations and rejects evidence that changes mid-fetch.
  • Distinguishes exact Keychain match, mismatch, proven absence, and unavailable comparison.
  • Confirms new owner-to-account bindings with two stable exact-match observations; existing bindings quarantine stale samples after an observed account switch.
  • Keeps supported file-only and default background refreshes recording into their credential-owned bucket when no authoritative binding exists.
  • Preserves explicit/environment credentials and proven refresh-token rotation lineage.
  • Stores only hashed account identities and opaque credential-owner identifiers.

This intentionally does not change visible menu snapshot selection. #1886 should remain open for that remaining behavior.

Proof

  • swift test --filter 'ClaudeOAuthHistoryCredentialRoutingTests|UsageStorePlanUtilizationClaudeIdentity' — 37 tests passed across 3 suites.
  • make check — clean; SwiftFormat and strict SwiftLint passed.
  • make test — all 47 shards passed.
  • /Users/steipete/Projects/agent-skills/skills/autoreview/scripts/autoreview --mode branch --base origin/main — clean; no accepted/actionable findings.

Exact reviewed head: bfe9ccda2ba7c07db6648ba246e1a63eed2cfe99

Notes

  • No UI change; no screenshots required.
  • No dependency or Keychain-prompt policy change.
  • Contributor live reproduction established the original same-directory /login behavior; maintainer hardening and isolated regression tests cover the final evidence boundary.

@clawsweeper

clawsweeper Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codex review: found issues before merge. Reviewed July 5, 2026, 8:42 AM ET / 12:42 UTC.

Summary
The PR adds Claude OAuth active-account corroboration, persisted owner/account binding state, Keychain match-state plumbing, stale-sample quarantine, and focused regression coverage for same-directory Claude account switches.

Reproducibility: yes. The linked issue and PR discussion give a live same-directory Claude /login reproduction, and source inspection shows current main keys history from the served OAuth owner; I did not run live Keychain validation because AGENTS.md warns against prompt-triggering checks.

Review metrics: 3 noteworthy metrics.

  • Changed files: 11 files affected. The patch spans app history routing, provider fetch result plumbing, Claude OAuth credential matching, and tests.
  • Persisted state: 2 UserDefaults maps added. The owner binding and candidate maps are the upgrade-sensitive part maintainers should notice before merge.
  • Reported validation: 47 shards plus make check. The PR body reports the full local suite and strict formatting/lint checks for this broad provider/history change.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #1886
Summary: This PR is the candidate fix for the Claude same-directory account-switch history misattribution tracked by the canonical issue; related Claude auth items are adjacent rather than replacements.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦞 diamond lobster
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Decide the established-owner rebind policy and add the regression test for a mapped owner observed with a different active Claude account.

Risk before merge

  • [P1] Merging this PR changes persisted Claude OAuth owner/account binding state for existing users, so upgrade behavior is compatibility-sensitive.
  • [P1] The current head can overwrite an established owner-to-account binding after two exact-Keychain observations with a different active account identity, which may weaken the stale-owner quarantine during non-atomic account switches.
  • [P1] The remaining behavior is in the Claude auth-provider path, where regressions can misattribute plan-utilization history across accounts.

Maintainer options:

  1. Keep established bindings authoritative (recommended)
    Remove or narrow the existing-binding rebind path so a mapped owner with a different active account remains quarantined, then add the regression case.
  2. Accept the rebind behavior
    Merge only if maintainers intentionally accept that exact-Keychain evidence may repair an established owner mapping across active-account identities.
  3. Pause for live upgrade proof
    Hold the PR until a real two-account upgrade run demonstrates the rebind path cannot occur during Claude Code account-switch ordering.

Next step before merge

  • [P2] The remaining blocker is a maintainer-owned compatibility choice about whether established Claude owner bindings may be repaired or must stay authoritative.

Maintainer decision needed

  • Question: Should established Claude OAuth history owners ever be rebound to a different active Claude account, or should mismatches always remain quarantined once a binding exists?
  • Rationale: The current code intentionally repairs an existing binding after two exact-Keychain observations, but that is the same compatibility-sensitive state used to prevent stale-owner history contamination during non-atomic account switches.
  • Likely owner: steipete — He authored the final branch hardening and requested the latest re-review around this Claude history ownership path.
  • Options:
    • Preserve established bindings (recommended): Keep existing owner mappings authoritative and quarantine mismatched active-account observations until a different credential owner is seen.
    • Accept rebinding repair: Keep the current repair path and own the risk that a transient active-account/Keychain skew can reclassify an established owner.

Security
Cleared: No concrete security or supply-chain issue was found; the diff adds no dependencies, does not relax the Keychain prompt boundary, and persists hashed account identities rather than raw token material.

Review findings

  • [P2] Do not rebind established Claude owners — Sources/CodexBar/UsageStore+PlanUtilization.swift:1026
Review details

Best possible solution:

Preserve established owner bindings once set, quarantine mismatched active-account observations, and allow new owners to bind only from corroborated evidence with focused regression coverage.

Do we have a high-confidence way to reproduce the issue?

Yes. The linked issue and PR discussion give a live same-directory Claude /login reproduction, and source inspection shows current main keys history from the served OAuth owner; I did not run live Keychain validation because AGENTS.md warns against prompt-triggering checks.

Is this the best way to solve the issue?

Not yet. The overall design is a narrow maintainable fix, but the established-binding rebind path should be resolved before merge because it can weaken the quarantine that the fix relies on.

Full review comments:

  • [P2] Do not rebind established Claude owners — Sources/CodexBar/UsageStore+PlanUtilization.swift:1026
    Late discovery: when an owner is already bound to account A, this branch can accept two exact-Keychain observations while ~/.claude.json reports account B and overwrite the binding to B. That makes later stale-owner samples look valid under the new account and defeats the quarantine; established owner mismatches should stay quarantined instead of being rebound.
    Confidence: 0.79
    Late finding: first raised on code an earlier review cycle already covered.

Overall correctness: patch is incorrect
Overall confidence: 0.79

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 780767953539.

Label changes

Label changes:

  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The PR body and comments include after-fix proof from a real two-account Claude setup plus focused and full-suite validation, which is sufficient for this internal history-routing change.
  • remove rating: 🦞 diamond lobster: Current PR rating is rating: 🦐 gold shrimp, so this older rating label is no longer current.
  • remove status: 👀 ready for maintainer look: Current PR status label is status: ⏳ waiting on author.

Label justifications:

  • P2: This is a bounded Claude OAuth history-attribution fix with account-history impact but no crash, security bypass, or unusable runtime.
  • merge-risk: 🚨 compatibility: The PR adds persisted owner-binding state and changes upgrade/background history-write behavior for existing Claude OAuth users.
  • merge-risk: 🚨 auth-provider: The PR changes Claude OAuth identity corroboration and history routing using Keychain-derived evidence plus active Claude account UUID state.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The PR body and comments include after-fix proof from a real two-account Claude setup plus focused and full-suite validation, which is sufficient for this internal history-routing change.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body and comments include after-fix proof from a real two-account Claude setup plus focused and full-suite validation, which is sufficient for this internal history-routing change.
Evidence reviewed

What I checked:

Likely related people:

  • steipete: He introduced the current-main Claude plan-utilization history separation and authored the later hardening commits on this PR branch. (role: recent area contributor and final hardening author; confidence: high; commits: d5f1c58789cd, 70dc617a4ee5, bfe9ccda2ba7; files: Sources/CodexBar/UsageStore+PlanUtilization.swift, Sources/CodexBar/UsageStore+Refresh.swift, Tests/CodexBarTests/UsageStorePlanUtilizationClaudeIdentityTests.swift)
  • Rajvardhan Patil: History shows earlier Claude CLI token ownership work in the OAuth credential store extended by this PR. (role: adjacent feature contributor; confidence: medium; commits: 3488587856c7; files: Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift)
  • Yuxin Qiao: He authored the merged background Claude delegated-refresh and Keychain prompt guard that this PR preserves. (role: adjacent auth-provider contributor; confidence: medium; commits: a27c9b094e61; files: Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift, Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.swift)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (8 earlier review cycles)
  • reviewed 2026-07-04T20:15:15.214Z sha b68895d :: needs real behavior proof before merge. :: [P1] Handle unmapped stale owners before writing
  • reviewed 2026-07-04T20:51:38.226Z sha 8bf824d :: needs maintainer review before merge. :: none
  • reviewed 2026-07-04T20:57:39.118Z sha 8bf824d :: needs maintainer review before merge. :: none
  • reviewed 2026-07-04T21:27:51.098Z sha 8bf824d :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T10:35:03.557Z sha 70dc617 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T10:42:21.459Z sha 70dc617 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T10:46:25.728Z sha 70dc617 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T12:37:39.098Z sha bfe9ccd :: needs maintainer review before merge. :: none

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b68895d1ac

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +173 to +177
} else if map[owner] == nil, let currentUuid {
// First sighting of this owner: bind it to the account active right now.
// Never OVERWRITE on mismatch (that is the stale case handled above).
map[owner] = currentUuid
self.persistClaudeOAuthAccountUuidMap(map)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid binding migrated Claude owners to the new UUID

When this new ClaudeOAuthHistoryOwnerAccountUuidMapV1 map is empty (for example on the first run after upgrading) but planUtilizationHistory already contains a bucket for the old OAuth owner, a background poll after /login can still receive the stale old historyOwnerIdentifier while ~/.claude.json already reports the new account UUID. This branch treats that stale owner as a first sighting, stores oldOwner -> newUuid, and then records the sample under the old owner's history, so the same same-directory switch mis-attribution still happens for migrated users until the owner had previously been observed by this new code.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. labels Jul 4, 2026
@ss251

ss251 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews. Addressed the P2 (empty-map first-sighting / map poisoning) flagged by both Codex and ClawSweeper in 8bf824dc:

  • The first-sighting owner → accountUuid binding is now created only on a user-initiated refresh (ProviderInteractionContext.current == .userInitiated), where the keychain freshness sync guarantees the served owner reflects the active account. Background polls honor an existing binding (quarantine on mismatch) but never create one, and an unmapped background read fails open with no persisted binding — so the map can no longer be poisoned. New regression test background first sighting does not poison the account map, verified to fail if the trustworthy-read gate is removed.
  • Documented the accepted warm-up limitation (ClawSweeper maintainer option 2): the safeguard arms after the first user-initiated sample on an account; the narrow window before that fails open (same as pre-fix) but never poisons the map.

Real-behavior proof (now in the PR body): ran the patched recordPlanUtilizationHistorySample against real identity data on a two-account machine, prompt-free (plain-file reads only) — real owner (app-formula SHA256 of the real refresh token, never printed), active account live-read from the real ~/.claude.json, account B's real accountUuid captured from an actual /login switch. The stale background sample after the switch is not written to the old bucket (history stays [[30.0]], unscoped empty), and the empty-map upgrade path creates no binding.

make test (47 shards) + make check green; all Claude-identity + plan-utilization tests pass.

@clawsweeper re-review

@ss251

ss251 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@clawsweeper

clawsweeper Bot commented Jul 4, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8bf824dccb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +174 to +176
} else if map[owner] == nil,
let currentUuid,
ProviderInteractionContext.current == .userInitiated

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle upgraded OAuth owners before background switches

When a user already has Claude OAuth history from before this version, the new UUID map starts empty; if they then switch Claude accounts before any explicit user-initiated refresh, normal timer/startup refreshes still run with ProviderInteractionContext.current == .background (see UsageStore.swift around the refresh task setup), so this branch leaves effectiveOwner as the stale owner and appends the new account’s background sample to the previous account’s bucket. That leaves the account-switch contamination unfixed for the upgrade path unless the user happens to force a refresh first; consider quarantining or seeding a binding for known scoped owners instead of failing open on background first-sightings that already have history.

Useful? React with 👍 / 👎.

@ss251

ss251 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Re: the follow-up P2 on the empty-map upgrade window — this is a deliberate, documented tradeoff rather than an oversight, and I want to lay out why fail-open is the default (with the quarantine variant ready if maintainers prefer it).

The genuine regression this PR introduced — map poisoning (binding a stale owner to the new UUID) — is fixed: background polls never create a binding. What remains is the narrow warm-up window where an upgraded user switches accounts before the map is warmed by any user-initiated refresh; there the background sample fails open, identical to pre-fix behavior.

I considered the suggested quarantine-on-known-scoped-owner, but it has a broad downside: shouldRecordPlanUtilizationHistory returns true for .claude unconditionally, so background polls record Claude history. Quarantining background first-sightings that already have a bucket would stall plan-utilization history for every user who relies on the menu-bar text and rarely opens the menu (their owner stays unmapped, so every post-first background sample would be dropped) — turning a rare, self-healing contamination into a broad ongoing history gap. That's a worse default for a usage-history feature.

So: fail-open keeps history continuous and never poisons the map; the residual contamination is bounded to the pre-warm-up window and self-heals on the first user-initiated refresh. If the maintainers would rather guarantee no cross-account contamination at the cost of that background-history gap, the quarantine variant is a ~10-line follow-up (drop background first-sightings whose owner already has a scoped bucket) — happy to add it. That's the product call framed as ClawSweeper's option 1 vs 2.

@ss251

ss251 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-run

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 4, 2026
@ss251

ss251 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

clawsweeper's option 1 vs 2, condensed (full rationale in the PR description):

  • Fail-open over quarantine: background polls also record plan-utilization history (shouldRecordPlanUtilizationHistory), so quarantine would stall history for menu-bar-only users. A broad ongoing gap vs. a rare one-shot mis-write.
  • Residual risk is bounded: only an upgraded user who switches accounts before any user-initiated refresh — worst case one write to the old bucket, self-healed on the first refresh. Never worse than today, strictly better after warm-up.
  • On the two merge-risk labels: no keychain gate touched (Fix #1844: block background Claude delegated refresh and mcpOAuth-only keychain touch #1848 boundary intact — only ~/.claude.json is read, prompt-free); the UserDefaults map is purely additive (absent map = current behavior).

If you prefer guaranteed no-contamination, the quarantine variant (option 1) is ~10 lines — I can push it same-day.

ss251 and others added 3 commits July 5, 2026 11:18
…1886)

Background polls keep Claude-Keychain access gated (steipete#1848), so the credential
cache can serve a previous account's stale credential after an in-place
/login switch on the shared ~/.claude, mis-attributing usage to the previous
account's plan-utilization history bucket. Corroborate the served history
owner against the active account in ~/.claude.json (prompt-free, never gated)
and quarantine the sample on a positive mismatch; recover on the next
user-initiated refresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ds (steipete#1886)

A background poll can serve a previous account's stale credential (the keychain
freshness sync is gated off in background), so binding an unmapped owner to the
currently-active account on a background first sighting could poison the
owner->accountUuid map for upgraded users who already have owner-keyed history.
Gate the first-sighting binding on ProviderInteractionContext == .userInitiated,
where the freshness sync guarantees the served owner reflects the active account.
Background polls still honor an existing binding (quarantine on mismatch) but
never create one; unmapped background reads fail open. Addresses the Codex and
ClawSweeper P2 on the empty-map upgrade path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@steipete steipete force-pushed the fix/claude-account-switch-same-dir-history branch from 8bf824d to 70dc617 Compare July 5, 2026 10:31
@steipete

steipete commented Jul 5, 2026

Copy link
Copy Markdown
Owner

@clawsweeper re-review

Maintainer repair is now on current main at exact head 70dc617a4ee5bd88090225ee50191d80daac34de. The owner map now arms only from an exact current-Keychain credential match; user interaction alone cannot bind stale ownership. Focused tests, all 47 local shards, make check, and AutoReview are green.

@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jul 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bfe9ccda2b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return nil
}
// Two stable exact-Keychain observations repair a binding poisoned by a non-atomic login.
map[evidence.owner] = currentAccountIdentity

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not rebind an established Claude owner

When an owner is already bound to account A, a non-atomic /login can make ~/.claude.json report account B while the current Keychain/cache still serves owner A; after two such stable observations this branch overwrites map[ownerA] with B and returns the owner, so future background stale-owner samples see mapped == currentAccountIdentity and append account B's usage into owner A's history bucket. Established owner mismatches should stay quarantined rather than repairing the binding to a different active UUID.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 5, 2026
@steipete steipete merged commit 9a00284 into steipete:main Jul 5, 2026
10 checks passed
@steipete

steipete commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Landed as 9a002844, with the release-note closeout in cda65e70.

Exact-head proof for bfe9ccda:

  • GitHub CI: all 10 checks green, including four macOS shards, both Linux CLI jobs, lint, aggregate gate, and GitGuardian.
  • Focused history/identity proof: 37 tests across ClaudeOAuthHistoryCredentialRoutingTests and UsageStorePlanUtilizationClaudeIdentity* passed.
  • Full local suite: all 47 shards passed.
  • make check: passed; SwiftFormat clean and SwiftLint reported 0 violations.
  • Autoreview: clean, no accepted/actionable findings.

This lands the history-isolation portion only. #1886 remains open for the separate visible-menu account-selection problem.

Thanks @ss251!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants