claude-multiacc 1.0.13 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +249 -14
- package/bin/claude +402 -38
- package/bin/claude-accounts +141 -45
- package/bin/codex +254 -26
- package/bin/codex-accounts +157 -52
- package/install.sh +175 -69
- package/lib/common.sh +262 -2
- package/lib/credential.py +336 -0
- package/lib/report.py +355 -0
- package/package.json +1 -1
- package/tests/run-tests.sh +1053 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ completely independent — separate manifests, credentials, telemetry, and logs
|
|
|
16
16
|
either provider can be used, re-authenticated, or emptied without touching the other.
|
|
17
17
|
|
|
18
18
|
Tested on: macOS (bash 3.2, zsh, Claude Code 2.1.207+, Codex CLI 0.147) and Ubuntu 24.04
|
|
19
|
-
(bash 5.2). Test suite: `tests/run-tests.sh` (
|
|
19
|
+
(bash 5.2). Test suite: `tests/run-tests.sh` (525 sandboxed tests, no network / no
|
|
20
20
|
quota — passes on both platforms).
|
|
21
21
|
|
|
22
22
|
---
|
|
@@ -44,8 +44,11 @@ repo bin/ first on PATH (rc-file block) /root/claude-multiacc/ (addon repo)
|
|
|
44
44
|
selection.
|
|
45
45
|
4. Among the accounts that remain valid **on this machine** (`.credentials.json` or
|
|
46
46
|
`server.token` present) and not limit-excluded, pick the one with the **most remaining
|
|
47
|
-
headroom** (see below).
|
|
48
|
-
|
|
47
|
+
headroom** (see below). On equal headroom the pool **rotates away from the account it
|
|
48
|
+
just handed out** and samples the rest at random — so quitting a session and starting
|
|
49
|
+
another one moves you along the pool, while a burst of parallel `claude -p` runs still
|
|
50
|
+
spreads (a strict "oldest first" order would send every member of the burst to the same
|
|
51
|
+
account). `CLAUDE_SHIM_SELECT=random` restores plain uniform-random selection.
|
|
49
52
|
5. If every account is limit-excluded → the highest-headroom *authenticable* account
|
|
50
53
|
anyway + a warning in `selection.log` (degraded beats down: the 100% rule).
|
|
51
54
|
6. If nothing is usable at all → stock passthrough, with the reason in `selection.log`
|
|
@@ -98,9 +101,9 @@ The shim prints nothing, logs `timestamp account cwd` (never prompt text) to
|
|
|
98
101
|
byte-identically. If anything is missing (no manifest, no accounts, unreadable state,
|
|
99
102
|
even an unset `HOME`) it fails **open** into plain passthrough.
|
|
100
103
|
|
|
101
|
-
**Limit-aware marking.** `claude-accounts limits` (every
|
|
104
|
+
**Limit-aware marking.** `claude-accounts limits` (every 5 min via launchd on the Mac,
|
|
102
105
|
cron on the server, plus an opportunistic non-blocking kick from the shim when data is
|
|
103
|
-
>
|
|
106
|
+
>10 min stale) reads each account's own OAuth usage endpoint — the same data `/usage`
|
|
104
107
|
shows. Every bucket the endpoint returns is tracked separately: `session`, `weekly_all`,
|
|
105
108
|
and per-model scoped buckets — **`weekly_scoped:Fable` is its own bucket**. Any bucket
|
|
106
109
|
≥ 90% ⇒ the account gets a `.limited` marker (with the bucket's reset time) and leaves the
|
|
@@ -114,9 +117,12 @@ skipped, an all-legacy payload falls back to `five_hour`/`seven_day`, and a payl
|
|
|
114
117
|
code cannot read at all degrades that one account (fail open), never the run.
|
|
115
118
|
|
|
116
119
|
Telemetry failures never block work: no fresh data ⇒ account treated as available. The
|
|
117
|
-
endpoint rate-limits per account, so the refresher skips accounts fetched in the last
|
|
120
|
+
endpoint rate-limits per account, so the refresher skips accounts fetched in the last 4 min
|
|
118
121
|
and backs off exponentially (honoring `Retry-After`) on a 429 — `limits --force` overrides
|
|
119
|
-
both.
|
|
122
|
+
both. The cadence is deliberately unhurried: several machines polling the same accounts
|
|
123
|
+
every minute earns a 429 with `Retry-After: 3600`, and telemetry then goes stale for an
|
|
124
|
+
hour at a time — which is exactly when every account starts scoring *neutral* and the
|
|
125
|
+
picker loses its ability to tell them apart. If an account's OAuth access token has been expired for a while (idle account,
|
|
120
126
|
nothing ran claude under it for hours), the refresher renews it directly via the OAuth
|
|
121
127
|
**refresh-token grant** — the same endpoint and public client id Claude Code itself uses —
|
|
122
128
|
and atomically persists the rotated credential (0600) back to that account's
|
|
@@ -127,6 +133,61 @@ account would lose to a busy-but-fresh one. Refresh failures fail open and back
|
|
|
127
133
|
then says re-login is needed). Overrides: `CLAUDE_MULTIACC_TOKEN_URL`,
|
|
128
134
|
`CLAUDE_MULTIACC_CLIENT_ID` (used by the sandboxed tests; defaults are correct for real use).
|
|
129
135
|
|
|
136
|
+
**Limits the client reports itself.** The usage endpoint is not the only source of
|
|
137
|
+
truth, and it is the one that fails exactly when it matters. Claude Code records every
|
|
138
|
+
rejection in its session transcript:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{"error":"rate_limit","apiErrorStatus":429,
|
|
142
|
+
"quotaLimits":{"status":"rejected","resetsAt":1787333400,"rateLimitType":"five_hour"}}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
That record is free, offline, instant, and carries the **real** reset time the API handed
|
|
146
|
+
out — so the shim reads it. This is what makes an **interactive** session take its own
|
|
147
|
+
account out of the pool: auto-retry below only ever sees `-p` runs, so before this a 5-hour
|
|
148
|
+
limit hit in a tmux session left no trace at all, and the next `claude` could walk straight
|
|
149
|
+
back into the same dead account.
|
|
150
|
+
|
|
151
|
+
Transcripts are not account-scoped (`<acct>/projects` is a shared symlink by design), so
|
|
152
|
+
the session → account mapping comes from `<acct>/sessions/<pid>.json`, which the client
|
|
153
|
+
maintains only while a run is alive. The shim spawns a short detached poll before `exec`
|
|
154
|
+
(the pid survives `exec`, so it knows the filename) that records the session id — with the
|
|
155
|
+
session's own start time — in `<acct>/.sessions-index`; selection then tail-scans those
|
|
156
|
+
transcripts.
|
|
157
|
+
|
|
158
|
+
Everything about the rule is built to under-claim rather than over-claim, because a
|
|
159
|
+
wrongly parked account is worse than a missed limit:
|
|
160
|
+
|
|
161
|
+
- **One owner per session.** `claude --continue` resumes the *same* session id under
|
|
162
|
+
whichever account the pool hands out next (only `--fork-session` mints a new one), and
|
|
163
|
+
the transcript is shared. Claiming an id releases it from every other account — and if
|
|
164
|
+
two accounts end up holding the same id anyway (a crossed race, corrupt state), the
|
|
165
|
+
ambiguity means neither of them answers for it.
|
|
166
|
+
- **Only from the handover.** A rejection recorded before this account took the session
|
|
167
|
+
over stays with the account that earned it; a record that cannot be dated is not
|
|
168
|
+
attributed at all.
|
|
169
|
+
- **Only a private session tree.** If `<acct>/sessions` — or the account dir itself, or a
|
|
170
|
+
directory nested inside it — is a symlink into somewhere shared, the runs in it prove
|
|
171
|
+
nothing about who spent the quota, so the scan stays off for that account entirely.
|
|
172
|
+
- A rejection whose `resetsAt` has already passed is history and excludes nothing; a
|
|
173
|
+
hostile or unreadable index entry is skipped; `CLAUDE_MULTIACC_CLIENT_LIMITS=0` turns
|
|
174
|
+
the whole scan off.
|
|
175
|
+
|
|
176
|
+
The resulting marker is tagged `reason=client-rate-limit` and outlives a clean telemetry
|
|
177
|
+
pass while its window is still open — first-hand evidence beats a usage payload that
|
|
178
|
+
disagrees. Cost is bounded: the three newest sessions per account, a tail read rather than
|
|
179
|
+
a whole transcript, and a clean result memoized for 20s
|
|
180
|
+
(`CLAUDE_MULTIACC_CLIENT_SCAN_TTL`), which on a three-account pool of large live
|
|
181
|
+
transcripts is ~2 ms per run amortized.
|
|
182
|
+
|
|
183
|
+
The codex shim does the same from rollout files
|
|
184
|
+
(`$CODEX_HOME/sessions/<Y>/<M>/<D>/rollout-*.jsonl` carry
|
|
185
|
+
`"rate_limits":{"primary":{"used_percent":…,"resets_at":…}}`, read newest-first), under the
|
|
186
|
+
same ownership rule. Note that the **installed layout deliberately shares that tree**
|
|
187
|
+
(`<acct>/sessions -> ~/.codex/sessions`, so `codex resume` finds every session), so on a
|
|
188
|
+
default codex pool this scan stays off and the usage endpoint remains codex's only limit
|
|
189
|
+
signal. Claude accounts keep a private `sessions/` registry, so it is fully active there.
|
|
190
|
+
|
|
130
191
|
**Auto-retry** (`-p`/`--print` only, default on, `CLAUDE_SHIM_RETRY=0` disables): on an
|
|
131
192
|
auth- or rate-limit-looking failure the shim retries once on a different account and
|
|
132
193
|
marks the failed one — a **10-minute cooldown** for a rate limit (it heals on its own),
|
|
@@ -153,7 +214,7 @@ above about the claude pool holds for the codex pool with these translations:
|
|
|
153
214
|
| `CLAUDE_ACCOUNT` / `CLAUDE_SHIM_RETRY` / `CLAUDE_MULTIACC_DISABLE` / … | `CODEX_ACCOUNT` / `CODEX_SHIM_RETRY` / `CODEX_MULTIACC_DISABLE` / … |
|
|
154
215
|
|
|
155
216
|
Same selection rule (weekly headroom primary, session tiebreak, ≥90% any-bucket
|
|
156
|
-
exclusion), same marker semantics (`.limited` cooldowns, `.expired` parks with
|
|
217
|
+
exclusion, equal scores rotate), same marker semantics (`.limited` cooldowns, `.expired` parks with
|
|
157
218
|
credential/policy scoping and soft expiry), same fail-open guarantees, same sync
|
|
158
219
|
safety guards. The `codex` shim engages the buffered auto-retry only for
|
|
159
220
|
`codex exec` runs with finite stdin, exactly like `-p` on the claude side.
|
|
@@ -252,7 +313,7 @@ What install does (all reversible, nothing else):
|
|
|
252
313
|
- **macOS:** marked PATH block at the END of `~/.zshenv`, `~/.zprofile`, `~/.zshrc`
|
|
253
314
|
(+ bash rc files if present) — end-of-file placement matters because those files
|
|
254
315
|
re-prepend `~/.local/bin`; launchd agents `com.claude-multiacc.limits` +
|
|
255
|
-
`.codex-limits` (
|
|
316
|
+
`.codex-limits` (5m), `.health` + `.codex-health` (weekly Mon morning), and
|
|
256
317
|
`.update` (daily 04:07). Warns if this Mac stores credentials in the Keychain
|
|
257
318
|
(file-based credentials are required for per-dir logins).
|
|
258
319
|
- **Linux (root):** PATH block in `~/.bashrc` + `/etc/profile.d/claude-multiacc.sh`,
|
|
@@ -368,8 +429,9 @@ manually via:
|
|
|
368
429
|
claude-accounts sync # and codex-accounts sync for the codex pool
|
|
369
430
|
```
|
|
370
431
|
|
|
371
|
-
Targets are the
|
|
372
|
-
|
|
432
|
+
Targets are the resolved sync target — env override, else the manifest's
|
|
433
|
+
`server`/`server_root`/`server_repo` (the Linux box), see *Pointing sync somewhere else*
|
|
434
|
+
below — plus an optional `peers` array for additional machines (e.g. a second Mac):
|
|
373
435
|
|
|
374
436
|
```json
|
|
375
437
|
"peers": [{"target": "gas@gas-mini", "root": "/Users/gas/.claude-accounts",
|
|
@@ -392,6 +454,174 @@ set, which is what prevents two machines from overwriting each other's manifests
|
|
|
392
454
|
Make account changes on the source; sign-ins (`login`/`relogin`) still happen on
|
|
393
455
|
whichever machine needs the credential.
|
|
394
456
|
|
|
457
|
+
### Pointing sync somewhere else (or nowhere)
|
|
458
|
+
|
|
459
|
+
The target is no longer baked in. In precedence order:
|
|
460
|
+
|
|
461
|
+
1. `CLAUDE_MULTIACC_SYNC_TARGET` / `CODEX_MULTIACC_SYNC_TARGET` (or the provider-neutral
|
|
462
|
+
`MULTIACC_SYNC_TARGET`), plus `…_SYNC_ROOT` and `…_SYNC_REPO` — env, so a daemon can
|
|
463
|
+
point one instance at its own target without rewriting a manifest it does not own;
|
|
464
|
+
2. the manifest's `server` / `server_root` / `server_repo` (what `install.sh --server`
|
|
465
|
+
wrote);
|
|
466
|
+
3. the historical default — unchanged, so existing installs keep pushing where they did.
|
|
467
|
+
|
|
468
|
+
A pool whose `sync-role` file says `replica` still never pushes, in any mode — the
|
|
469
|
+
marker is honored and reported before anything else, and local-only mode below only
|
|
470
|
+
narrows it further (a local pool pushes nothing whether or not it is a replica).
|
|
471
|
+
|
|
472
|
+
The value **`none`** (also `local`, `off`, `disabled`, empty) selects **local-only** mode:
|
|
473
|
+
there is no ssh target at all because something else — the app-robot panel and its runner
|
|
474
|
+
daemon — distributes the pool. `sync` then validates the manifest, re-seeds account dirs,
|
|
475
|
+
fixes permissions and pushes nowhere; mutations stop auto-pushing (and stop warning about
|
|
476
|
+
a server they are not supposed to reach). One run can force it with `sync --no-server`,
|
|
477
|
+
and a whole install can start that way:
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
./install.sh --no-server # manifest server = none
|
|
481
|
+
claude-accounts sync # "sync ok (local-only: … nothing pushed)"
|
|
482
|
+
claude-accounts sync --no-server # same, whatever the manifest says
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
## Fleet integration (panels, daemons, multiple instances)
|
|
486
|
+
|
|
487
|
+
Everything below is non-interactive and machine-readable: it is what a supervising panel
|
|
488
|
+
(app-robot) or any other daemon needs to run a pool across a fleet of Macs.
|
|
489
|
+
|
|
490
|
+
### `--json` on `list`, `status` and `limits`
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
claude-accounts list --json # pool + accounts, no refresh
|
|
494
|
+
claude-accounts status --json # the same, plus last_picked and credential detail
|
|
495
|
+
claude-accounts limits --json # refresh usage first, then emit the same document
|
|
496
|
+
codex-accounts list --json # identical schema, provider: "codex"
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Stdout is the JSON document and nothing else. One shape for every verb and both
|
|
500
|
+
providers (`lib/report.py`), so a consumer writes one parser:
|
|
501
|
+
|
|
502
|
+
```jsonc
|
|
503
|
+
{
|
|
504
|
+
"schema": "claude-multiacc/pool.v1", // bumped only on a breaking change
|
|
505
|
+
"provider": "claude", "kind": "list", "generated_at": "…Z",
|
|
506
|
+
"machine": "mac", "host": "my-mini",
|
|
507
|
+
"pool": {
|
|
508
|
+
"root": "/Users/gas/.claude-accounts", "manifest": "…/accounts.json",
|
|
509
|
+
"threshold": 90,
|
|
510
|
+
"sync": {"mode": "server|local", "target": "root@…", "root": "…", "repo": "…",
|
|
511
|
+
"role": "source|replica", "peers": [...]}
|
|
512
|
+
},
|
|
513
|
+
"accounts": [{
|
|
514
|
+
"id": "acct-05", "email": "…", "home": "mac", "added_at": "…Z",
|
|
515
|
+
"home_dir": "/Users/gas/.claude-accounts/acct-05", "adopted": false,
|
|
516
|
+
"status": "active", // active|limited|expired|blocked|missing|remote
|
|
517
|
+
"state": "ok", "label": "OK", "reason": "…", "fix": "…",
|
|
518
|
+
"selectable": true, "needs_login": false,
|
|
519
|
+
"credential_class": "portable", // portable|machine-local|none
|
|
520
|
+
"portable": true,
|
|
521
|
+
"credentials": {"oauth": true, "token": true, "oauth_expires_at": "…Z",
|
|
522
|
+
"oauth_refresh_expires_at": "…Z", "token_minted_at": "…Z",
|
|
523
|
+
"token_age_days": 39},
|
|
524
|
+
"limited": false, "limit_reset_at": null, "limit_detail": null,
|
|
525
|
+
"usage": {"fetched_at": "…Z", "age_seconds": 41, "source": "oauth",
|
|
526
|
+
"max_percent": 62, "weekly_percent": 62, "session_percent": 18,
|
|
527
|
+
"buckets": [{"name": "weekly_scoped:Fable", "group": "weekly",
|
|
528
|
+
"percent": 62, "resets_at": "…Z"}]}
|
|
529
|
+
}],
|
|
530
|
+
"summary": {"total": 3, "active": 2, "limited": 1, "needs_login": 0,
|
|
531
|
+
"portable": 3, "selectable": 2},
|
|
532
|
+
"warnings": []
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
`status` values mean exactly what the shim does: only `active` accounts are selected;
|
|
537
|
+
`limited` authenticates but is parked until `limit_reset_at`; `expired`/`blocked`/
|
|
538
|
+
`missing` need a human (`fix` says what); `remote` is not logged in here on purpose.
|
|
539
|
+
A consumer that only distinguishes *usable / parked / needs-a-human* can read
|
|
540
|
+
`selectable` and `needs_login` and ignore the rest; `state` keeps the raw audit verdict
|
|
541
|
+
without the limit overlay. `limited` follows the shim's marker rule exactly — a marker
|
|
542
|
+
whose reset time has passed does not count, an unreadable one does.
|
|
543
|
+
|
|
544
|
+
### Which accounts are portable
|
|
545
|
+
|
|
546
|
+
| Credential | Where it lives | Class | Can it be copied to another machine? |
|
|
547
|
+
| --- | --- | --- | --- |
|
|
548
|
+
| claude setup-token | `<acct>/server.token` (`sk-ant-oat…`, ~1 year, inference-only) | **portable** | **Yes** — this is the only credential the fleet distributes |
|
|
549
|
+
| claude OAuth login | `<acct>/.credentials.json` | machine-local | **No.** Its refresh token rotates on every refresh; a second machine refreshing the same grant strands the first |
|
|
550
|
+
| codex ChatGPT login | `<acct>/auth.json` | machine-local | **No** — same rotating-refresh-token reason. Codex has **no** portable credential type |
|
|
551
|
+
|
|
552
|
+
Non-portable accounts are not a dead end: they show up as `credential_class:
|
|
553
|
+
"machine-local"`, and the way to run one somewhere else is one interactive sign-in there
|
|
554
|
+
(`claude-accounts login <id>`, `codex-accounts login <id>` — the codex device-code flow
|
|
555
|
+
works over SSH). A claude account can be *converted* to the portable class with one
|
|
556
|
+
`claude-accounts mint <id>` on a machine that is already signed in.
|
|
557
|
+
|
|
558
|
+
### `export-credential` / `import-credential`
|
|
559
|
+
|
|
560
|
+
```bash
|
|
561
|
+
claude-accounts export-credential acct-05 # blob to stdout
|
|
562
|
+
claude-accounts export-credential acct-05 --out cred.json # …or to a 0600 file
|
|
563
|
+
claude-accounts export-credential acct-05 --identity-only # registry entry, no secret
|
|
564
|
+
cat cred.json | claude-accounts import-credential # install it here
|
|
565
|
+
claude-accounts import-credential --in cred.json --home mac --no-sync
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
`export-credential` is **read-only on the source pool** — nothing is refreshed, re-minted
|
|
569
|
+
or marked, so exporting a live account cannot disturb it. It refuses anything that is not
|
|
570
|
+
portable, with the reason and the fix, and its exit code is the contract a daemon branches
|
|
571
|
+
on:
|
|
572
|
+
|
|
573
|
+
| Exit | Meaning |
|
|
574
|
+
| --- | --- |
|
|
575
|
+
| 0 | blob written |
|
|
576
|
+
| 2 | usage error / unknown account / malformed blob |
|
|
577
|
+
| 3 | the credential is machine-local — copying it would break both machines |
|
|
578
|
+
| 4 | the account has no credential material here |
|
|
579
|
+
| 5 | credential present but unusable (corrupt, or an API key rather than a setup-token) |
|
|
580
|
+
|
|
581
|
+
The blob is self-contained JSON (`format: "claude-multiacc/credential"`, version 1):
|
|
582
|
+
identity metadata (`id`, `email`, `home`, `added_at`), where it came from, and — for
|
|
583
|
+
class `portable` — the credential itself. **Treat a `portable` blob exactly like the
|
|
584
|
+
credential it contains.**
|
|
585
|
+
|
|
586
|
+
`import-credential` reads it on stdin (or `--in PATH`), creates the account dir and the
|
|
587
|
+
manifest entry as needed, writes the credential 0600, and clears any dead-auth marker.
|
|
588
|
+
It is idempotent **by email**: re-importing an account that is already in the pool
|
|
589
|
+
refreshes it in place rather than adding a second entry, so a daemon can push the same
|
|
590
|
+
pool to a machine over and over. It refuses a blob from the other provider and refuses an
|
|
591
|
+
API key. It also refuses to land an email in a second slot, or to write into a slot that
|
|
592
|
+
belongs to a different account — `--force` settles exactly those identity conflicts, and
|
|
593
|
+
nothing else: an **adopted** (symlinked) account is refused unconditionally, `--force`
|
|
594
|
+
included, because writing a credential through that link would put it outside the pool.
|
|
595
|
+
An `identity` blob registers the account and says which sign-in it still needs. The
|
|
596
|
+
imported account keeps the `added_at` it had on the source machine, so the same account
|
|
597
|
+
reads identically everywhere.
|
|
598
|
+
|
|
599
|
+
For codex, `export-credential` always refuses with exit 3 (there is nothing portable to
|
|
600
|
+
export) and points at the device-code login; `--identity-only` is how a codex account list
|
|
601
|
+
reaches another machine.
|
|
602
|
+
|
|
603
|
+
### Instance-scoped pools
|
|
604
|
+
|
|
605
|
+
`CLAUDE_ACCOUNTS_ROOT` / `CODEX_ACCOUNTS_ROOT` relocate a pool, so several app-robot
|
|
606
|
+
instances can share one machine and user account without ever seeing each other's
|
|
607
|
+
accounts. Both the CLIs **and the shims** resolve the same root, so a task launched with
|
|
608
|
+
the env set runs under that instance's pool. (The older `…_ACCOUNTS_DIR` spelling still
|
|
609
|
+
works; `…_ROOT` wins when both are set.)
|
|
610
|
+
|
|
611
|
+
```bash
|
|
612
|
+
export CLAUDE_ACCOUNTS_ROOT=/opt/app-robot/teamA/claude-accounts
|
|
613
|
+
export CODEX_ACCOUNTS_ROOT=/opt/app-robot/teamA/codex-accounts
|
|
614
|
+
./install.sh --no-server --instance teamA
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
An install whose roots are not the defaults is an **instance install**: its LaunchAgents
|
|
618
|
+
(and cron lines on Linux) are labelled `com.claude-multiacc.<instance>.*` so instances
|
|
619
|
+
never overwrite each other's agents, and they carry the pool roots in their environment.
|
|
620
|
+
It deliberately does **not** touch the shell rc PATH block, `/etc/profile.d`, or the
|
|
621
|
+
`/usr/local/bin` shims — one interactive PATH cannot serve two pools, and those belong to
|
|
622
|
+
the default install. `--instance NAME` labels it explicitly; otherwise the label is
|
|
623
|
+
derived from the pool root. Uninstalling an instance removes only that instance's agents.
|
|
624
|
+
|
|
395
625
|
## Pinning & env switches
|
|
396
626
|
|
|
397
627
|
| Env | Effect |
|
|
@@ -399,17 +629,22 @@ whichever machine needs the credential.
|
|
|
399
629
|
| `CLAUDE_ACCOUNT=acct-03` | pin this invocation to one account (wins over markers) |
|
|
400
630
|
| `CLAUDE_CONFIG_DIR=...` | shim passes straight through (scripts can pin the old way) |
|
|
401
631
|
| `CLAUDE_MULTIACC_DISABLE=1` | bypass selection entirely |
|
|
632
|
+
| `CLAUDE_MULTIACC_CLIENT_LIMITS=0` | do not read the client's own rate-limit records (same for `CODEX_MULTIACC_CLIENT_LIMITS`) |
|
|
633
|
+
| `CLAUDE_MULTIACC_CLIENT_SCAN_TTL=<s>` | how long a clean client-limit scan is memoized (default 20s; same for `CODEX_MULTIACC_CLIENT_SCAN_TTL`) |
|
|
402
634
|
| `CLAUDE_SHIM_RETRY=0` | disable the `-p` auto-retry |
|
|
403
|
-
| `
|
|
635
|
+
| `CLAUDE_ACCOUNTS_ROOT=...` | relocate the pool — one isolated pool per app-robot instance (legacy spelling: `CLAUDE_ACCOUNTS_DIR`) |
|
|
636
|
+
| `CLAUDE_MULTIACC_SYNC_TARGET=...` | sync target, overriding the manifest; `none` = local-only |
|
|
637
|
+
| `CLAUDE_MULTIACC_SYNC_ROOT=... / _SYNC_REPO=...` | remote pool root / addon repo that goes with it |
|
|
404
638
|
|
|
405
639
|
The codex shim honors the same switches spelled `CODEX_*`: `CODEX_ACCOUNT`,
|
|
406
640
|
`CODEX_HOME` (passthrough), `CODEX_MULTIACC_DISABLE`, `CODEX_SHIM_RETRY`,
|
|
407
|
-
`CODEX_SHIM_SELECT`, `
|
|
641
|
+
`CODEX_SHIM_SELECT`, `CODEX_ACCOUNTS_ROOT` (legacy `CODEX_ACCOUNTS_DIR`),
|
|
642
|
+
`CODEX_MULTIACC_SYNC_TARGET`, `CODEX_MULTIACC_THRESHOLD`.
|
|
408
643
|
|
|
409
644
|
## Verification
|
|
410
645
|
|
|
411
646
|
```bash
|
|
412
|
-
tests/run-tests.sh #
|
|
647
|
+
tests/run-tests.sh # 525 sandboxed tests, no quota
|
|
413
648
|
claude-accounts verify # real matrix: `claude -p "reply OK"` per authed account
|
|
414
649
|
claude-accounts verify --quick# auth presence/expiry only, no inference
|
|
415
650
|
claude-accounts limits # live per-bucket usage incl. the Fable bucket
|