Skip to content

fix(usage): label provider readings with their reporting Claude account - #355

Merged
mikehasa merged 1 commit into
mainfrom
fix/usage-two-accounts
Oct 3, 2026
Merged

mikehasa merged 1 commit into
mainfrom
fix/usage-two-accounts

Conversation

@mikehasa

@mikehasa mikehasa commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Two Claude accounts on one machine — the terminal CLI signed into one, the Claude desktop app into another — rendered as one client's contradictory meters on the Usage page. Readings were joined by client only, and the CLI statusLine stream carried no account identity, so a 7-day "100% · limit reached" from the desktop account sat unlabeled under the same claude-code row as the CLI's live 5-hour window.

What changed

  • The statusLine stream is attributed to its account at import time. The statusLine payload has no account field (checked against Claude Code 2.1.288), so the reading is attributed to the account the spool's config home is signed in as — email / account uuid / org uuid, read from that home's user-level .claude.json oauthAccount. Identity only; credentials are never opened. A relocated spool (explicit path, or AGENTACCT_STATUSLINE_SPOOL) claims no account rather than a guessed one.
  • Existing streams never fork. run_ids are unchanged, and the snapshot signature folds identity in only when present, so an account switch on the same spool records a fresh reading while codex / pre-enrichment streams keep their historical signatures (pinned by a test).
  • One label vocabulary, every surface. ClientLimit.account_label composes CLI · you@example.com / desktop app · org 07c55a50 (the same short-org form the limits header prints). The glance payload carries it additively beside stream_id; the macOS Usage page renders it above each reading's windows; the TUI prints it under the client name; agentacct limits adds account: … to the header. limits --json keeps its byte-stable shape, and readings with no provable identity render exactly as before (the committed fixtures carry no account data, so the visual references are untouched).

Validation

  • Python: 4,912 passed on CPython 3.11 locally (pytest -q -n 4 --dist loadfile); the same matrix runs on 3.11/3.12/3.13 in CI.
  • Swift: 678 tests, 7 environment-gated skips, 0 failures (swift test -c release); UsageCapacityTests 17/17 including the new two-account and no-invented-label cases.
  • End-to-end against this machine's real data (import into a temp store, real spool + real .claude.json): the recorded streams are now attributed — agentacct limits prints claude-code (desktop app) org: 07c55a50 and claude-code (CLI) account: huyx1325@gmail.com — the glance payload carries account_label for both, and the rendered Usage page shows the two labeled readings instead of four unlabeled meters. limits --json emits no new keys.
  • Visual check of the new rendering (temporary local expansion of the capacity disclosure, reverted): CLI · huyx1325@gmail.com and desktop app · org 07c55a50 each label their own Weekly / 5-hour rows.

The 0.12.11 version bump and rebuilt signed/notarized DMG follow in the separate release PR after this fix merges — the DMG embeds the frozen Python CLI, so this CLI-side change requires a rebuild.

Two Claude accounts on one machine rendered as one client's contradictory
meters: the Usage page joined provider-limit readings by client only, so the
CLI statusLine stream and the desktop plan-usage stream stacked unlabeled
under `claude-code`.

- The statusLine stream is attributed at import time to the account its config
  home is signed in as: email/account/org uuid only, read from that home's
  user-level `.claude.json` oauthAccount; credentials are never opened.
  Relocated spools (explicit path or AGENTACCT_STATUSLINE_SPOOL) claim no
  account rather than a guessed one.
- Snapshot signatures fold identity in only when present (an account switch
  records a fresh reading; codex and pre-enrichment streams keep their
  signatures), and run ids are unchanged so no existing stream forks.
- `ClientLimit.account_label` composes "CLI · you@example.com" /
  "desktop app · org 07c55a50"; the glance payload carries it additively next
  to stream_id, `agentacct limits` prints the account, and the TUI and macOS
  Usage page render it above each reading. `limits --json` keeps its
  byte-stable shape; identity-less readings render exactly as before.
@mikehasa
mikehasa merged commit dc71f1d into main Oct 3, 2026
4 checks passed
@mikehasa
mikehasa deleted the fix/usage-two-accounts branch October 3, 2026 12:37
@mikehasa mikehasa mentioned this pull request Oct 3, 2026
mikehasa added a commit that referenced this pull request Oct 3, 2026
Releases the Usage-page account attribution from #355 as **0.12.11**. Two Claude accounts on one machine (e.g. the terminal CLI signed into one, the desktop app into another) now render as two labeled provider-limit readings — `CLI · you@example.com`, `desktop app · org 07c55a50` — in the macOS app, the TUI, and `agentacct limits`, instead of four unlabeled meters under one client row.

This PR changes only `pyproject.toml` and `CHANGELOG.md`: release version, dated notes, and release links. App bundle versions derive from `pyproject.toml`.

### Validation

- Metadata, compatibility, and packaging guards: **66 passed** (`test_publish_workflow.py`, `test_rename_compat.py`, `test_packaging_release_guards.py`).
- Wheel builds as `agentacct-0.12.11-py3-none-any.whl` (`pip wheel --no-deps .`).
- `git diff --check` passed.
- Merge waits for pytest 3.11/3.12/3.13 and macOS app/UI CI on this exact head.

### DMG preflight

**Rebuild required.** Since v0.12.10, both `src/agentacct/` (account attribution and labels) and `apps/agentacct/` (the Usage-page reading labels) changed, and the DMG embeds the frozen Python CLI. Build a fresh signed, notarized, stapled DMG from this release PR's exact squash commit, embedding the matching 0.12.11 CLI. Verify the selected artifact and its independent public download before calling publication complete.

Additive-only on the wire: one new glance field (`account_label`) and rate-limit event metadata (`account_email` / `account_uuid` / `account_org`); `limits --json` is unchanged; no configuration, permission, or store-migration changes.
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