Skip to content

Attribute Claude desktop sessions to the account that ran them - #45

Merged
starkdmi merged 1 commit into
mainfrom
claude-desktop-account-attribution
Oct 8, 2026
Merged

starkdmi merged 1 commit into
mainfrom
claude-desktop-account-attribution

Conversation

@starkdmi

@starkdmi starkdmi commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Summary

The Claude desktop app runs Claude Code under its own sign-in and writes the same ~/.claude transcripts as the CLI. Every desktop session was attributed to the account cached in ~/.claude.json, which only the CLI updates, so after signing in to another account in the app its usage kept counting under the old one.

The app keeps one record per session under the account that ran it: claude-code-sessions/<account>/<organization>/local_<id>.json, naming the transcript (cliSessionId) and any it was resumed from (priorCliSessionIds).

  • Desktop session evidence. For the default ~/.claude source, the Claude adapter reads these records and binds each transcript to its account. Accounts seen only here are created from their uuid. The daemon watches the directory, so a record update rescans the source.
  • Copied sessions. Records are often copied into the new account's directory so chats stay visible, and either copy can be continued. Each record holds the session up to its lastActivityAt, so a session splits into stretches there:
    • A stretch only one record reaches is that account's for certain.
    • A stretch several reach is copied history. It goes to the account with the latest sign-in evidence (its directory's creation, or a session only it holds) and is marked as inferred.
    • A takeover starts once the previous account's trailing reply has had five minutes to land, sooner if the new account is seen active, and never before the new account's first sign-in.
  • Binding resolution. Conversation bindings gain optional active_from, observed_until and certain_until:
    • An event takes the binding that started latest at or before it.
    • A certain range outranks anything inferred later.
    • A binding stops claiming usage five minutes after its last recorded activity, so a session resumed in the CLI keeps the source's attribution.
    • Rescans can extend a binding's ranges and raise its confidence, never shrink them.
    • Codex bindings set none of these fields and resolve as before.
  • Incomplete snapshots. A session with an unreadable record, or a scan that cannot list a directory, yields no new desktop evidence; stored bindings stay as they are.
  • Evidence summaries now include accounts known only from bindings. Their kinds report as auth_snapshot, so the sync schema is unchanged.

Verification

  • cargo fmt -p, cargo clippy -p <crate> --all-targets -- -D warnings and cargo test -p for statsai-core, statsai-store, statsai-adapters and statsai
  • ./scripts/rust-ci.sh full, including statsai-daemon with --features watch
  • New adapter tests cover:
    • sessions held by one account;
    • untouched copies;
    • a copy continued under a new account;
    • an original continued after switching back;
    • accounts taking turns;
    • takeovers bounded by first sign-in and by quick switches;
    • resumed transcripts;
    • unreadable records and unlistable directories;
    • the source scoping of the sessions directory.
  • New store tests cover:
    • takeovers splitting one conversation;
    • certain ranges outranking later inferences, in both arrival orders;
    • range extension on rescan;
    • the cutoff after recorded activity, including a late reply and a CLI resume;
    • expired bindings not contesting live ones;
    • binding-only accounts in evidence summaries.
  • Ran the build against an isolated clone of a real store after an account switch in the desktop app:
    • sessions after the switch moved to the new account;
    • copied sessions stayed with the original;
    • a copied session continued under the new account split at the new account's sign-in;
    • a second scan left the bindings unchanged.

The Claude desktop app runs Claude Code under its own sign-in and writes
the same ~/.claude transcripts as the CLI, so every desktop session was
attributed to the account cached in ~/.claude.json, which only the CLI
updates. After signing in to another account in the app, its usage kept
counting under the old one.

The app keeps one record per session under the account that ran it, in
claude-code-sessions/<account>/<organization>/local_<id>.json, naming the
session's transcript and any earlier ones it was resumed from. The Claude
adapter now reads these records for the default ~/.claude source and
binds each transcript to its account, and the daemon watches the
directory.

Records are often copied into the new account's directory so chats stay
visible after a switch, and either copy can be continued. Each record
holds the session up to its lastActivityAt, so a session splits into
stretches there. A stretch only one record reaches is that account's for
certain. A stretch several reach is copied history: it goes to the
account with the latest sign-in evidence, its directory's creation or a
session only it holds, and is marked as inferred. A takeover starts once
the previous account's trailing reply has had five minutes to land,
sooner if the new account is seen active, and never before the new
account's first sign-in.

Conversation bindings gain optional active_from, observed_until and
certain_until. An event takes the binding that started latest at or
before it, a certain range outranks anything inferred later, and a
binding stops claiming usage five minutes after its last recorded
activity, so a session resumed in the CLI keeps the source's
attribution. Rescans can extend a binding's ranges and raise its
confidence but never shrink them. Codex bindings set none of these
fields and resolve as before.

A session with an unreadable record, or a scan that cannot list a
directory, yields no new desktop evidence. Evidence summaries now include
accounts known only from bindings.
@starkdmi
starkdmi merged commit 5adc549 into main Oct 8, 2026
4 checks passed
@starkdmi
starkdmi deleted the claude-desktop-account-attribution branch October 8, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant