Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions .claude/skills/verify/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ description: How to build, run, and E2E-verify PitStop changes — headless --ch
Replicates the "two accounts show the same usage" corruption safely:

1. Back up `~/.config/pitstop/profiles.json`.
2. Copy a real profile's blob:
`security find-generic-password -s "PitStop-profile" -a <real-email> -w`
2. Copy a real profile's blob using that row's `credentialAccount` from
`profiles.json`:
`security find-generic-password -s "PitStop-profile" -a <credentialAccount> -w`
3. File it under a fake email:
`security add-generic-password -s "PitStop-profile" -a poisoned-test@example.com -w "<blob>"`
and append a matching row to profiles.json (copy the real row, change
`email` + `oauthAccount.emailAddress`).
`email`, `oauthAccount.emailAddress`, `oauthAccount.organizationUuid`, and
`credentialAccount`).
4. Quit installed app, launch dev binary, wait one cycle.
5. Expect: fake keychain item deleted by the audit (`security find… -a
poisoned-test@example.com` exits 44), row gated in the menu with
Expand All @@ -46,9 +48,10 @@ Replicates "app relaunched during a 429" without touching the network:

1. Quit the installed app. `~/.config/pitstop/usage-cache.json` holds the
display state (dates are seconds since 2001-01-01 — unix minus 978307200).
2. Edit it: set `fetchError["<email>"] = "Rate limited"`,
`nextFetchAllowed["<email>"] = now + 600`, and age that account's
`usage[<email>].fetchedAt` back ~15 min.
2. Edit it using the row's provider-namespaced account key: set
`fetchError["<account-key>"] = "Rate limited"`,
`nextFetchAllowed["<account-key>"] = now + 600`, and age that account's
`usage[<account-key>].fetchedAt` back ~15 min.
3. Relaunch. Expect: the row still shows its bars, plus
"⚠ Rate limited — retrying in 9m · showing <time> data"; the account is
NOT re-fetched until the backoff passes (persisted backoff honored).
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ appear on [GitHub Releases](https://github.com/Livin21/pitstop/releases).

## [Unreleased]

### Fixed
- **Claude organizations sharing one email collapsed into one account.** Claude
profiles, keychain snapshots, usage state, Desktop merging, login validation,
and auto-switching are now keyed by email plus organization UUID. Team and
personal subscriptions using the same email display independent limits and
switch independently; the existing email-keyed snapshot is preserved during
migration, and subsequent organizations receive distinct keychain slots.

## [0.5.0] - 2026-07-16
### Added
- **Choose which limits trigger auto-switch.** Settings gains trigger
Expand Down
5 changes: 3 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ rides the Apple-signed `/usr/bin/security`.)
- Keep it pure AppKit plus the small SwiftUI settings window. The app
intentionally has **no third-party dependencies** — don't add one without a
strong reason.
- Per-account state is keyed by a provider-namespaced key (e.g. `codex:<email>`)
so accounts sharing an email across providers don't collide. Preserve that.
- Per-account state is keyed by a provider-namespaced stable identity (e.g.
`codex:<email>` or Claude's email + organization UUID) so accounts sharing
an email across providers or Claude organizations don't collide. Preserve that.

## Adding a provider

Expand Down
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ What shows up where:

- **Claude Code** accounts are switchable. **Claude Desktop** (a *different*
login) shows read-only, tagged **Desktop**; an account signed into both Code
and Desktop is one shared usage pool, so it stays a single row tagged
**Code · Desktop**.
and Desktop in the same organization is one shared usage pool, so it stays a
single row tagged **Code · Desktop**. The same email in multiple Claude
organizations stays separate, with its org/plan chip identifying each row.
- **OpenAI Codex** accounts are switchable too — its login lives in
`~/.codex/auth.json` (shared by the Codex CLI and the Codex app), which
PitStop snapshots per account and swaps the same way it swaps the Claude
Expand Down Expand Up @@ -134,7 +135,8 @@ Or set it up manually:
launch at login.
- **Accounts** are snapshots of the Claude Code credential blob:
- secrets live in the **keychain** (service `PitStop-profile`, one item
per account email) — never written to disk;
per email + Claude organization; upgraded installs retain their original
email-named item for the already-saved account) — never written to disk;
- non-secret identity (email, org, plan) lives in
`~/.config/pitstop/profiles.json`.
- **Claude Desktop** (the chat app) is read separately and read-only. It
Expand Down Expand Up @@ -194,17 +196,19 @@ Or set it up manually:

## Adding a second account

PitStop can only switch between accounts it has snapshotted, and it snapshots
whatever is *live* on each refresh — so seed each account by being logged into
it once while PitStop runs:
PitStop can only switch between accounts/organizations it has snapshotted, and
it snapshots whatever is *live* on each refresh — so seed each one by being
logged into it once while PitStop runs. This includes two Claude organizations
that use the same email:

1. PitStop auto-saves whatever account is currently live.
2. Sign in with the **other** account — Claude Code: run `/login`; Codex: run
`codex` and sign in (the CLI and the Codex app share this login); Gemini:
sign in from the `gemini` CLI or Antigravity (both share the Google login).
3. PitStop notices it on the next refresh and saves it too (for Claude you can
also click **Save Current Account**).
4. Both accounts now appear in the menu — click either to switch.
4. Both accounts now appear in the menu — same-email Claude rows are
distinguished by their org/plan chips — and you can click either to switch.

## What switching means for running sessions

Expand Down
Loading