Repository navigation
Attribute Claude desktop sessions to the account that ran them - #45
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Claude desktop app runs Claude Code under its own sign-in and writes the same
~/.claudetranscripts 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).~/.claudesource, 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.lastActivityAt, so a session splits into stretches there:active_from,observed_untilandcertain_until:auth_snapshot, so the sync schema is unchanged.Verification
cargo fmt -p,cargo clippy -p <crate> --all-targets -- -D warningsandcargo test -pforstatsai-core,statsai-store,statsai-adaptersandstatsai./scripts/rust-ci.sh full, includingstatsai-daemonwith--features watch