claude-multiacc 2.0.41 → 2.0.43

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.
Files changed (33) hide show
  1. package/README.md +60 -2
  2. package/bin/claude +414 -5
  3. package/bin/claude-accounts +36 -11
  4. package/bin/codex +406 -4
  5. package/docs/ACCOUNT_OPERATIONS.md +7 -1
  6. package/docs/AUTORESUME.md +321 -0
  7. package/docs/CODEX.md +12 -1
  8. package/docs/VERIFICATION.md +9 -1
  9. package/lib/__pycache__/audit.cpython-312.pyc +0 -0
  10. package/lib/__pycache__/autoresume.cpython-312.pyc +0 -0
  11. package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
  12. package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
  13. package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
  14. package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
  15. package/lib/__pycache__/mcp_registry.cpython-312.pyc +0 -0
  16. package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
  17. package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
  18. package/lib/__pycache__/shim_path.cpython-312.pyc +0 -0
  19. package/lib/autoresume.py +2271 -0
  20. package/lib/common.sh +24 -1
  21. package/lib/keychain.py +274 -59
  22. package/package.json +1 -1
  23. package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
  24. package/tests/__pycache__/test_autoresume.cpython-312.pyc +0 -0
  25. package/tests/__pycache__/test_claude_reset.cpython-312.pyc +0 -0
  26. package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
  27. package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
  28. package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
  29. package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
  30. package/tests/fake_security.sh +90 -0
  31. package/tests/run-tests.sh +1178 -67
  32. package/tests/test_autoresume.py +2565 -0
  33. package/tests/test_keychain.py +323 -50
@@ -0,0 +1,321 @@
1
+ # Auto-resume (interactive sessions in tmux)
2
+
3
+ An interactive `claude` or `codex` session that stops on a usage limit, a failed login, an
4
+ API error or (claude only) a crash is restarted automatically on another pooled account
5
+ with headroom. It keeps the **same session id, directory and flags**, and you press no
6
+ keys. This covers sessions launched from a shell inside **tmux**. Every other launch runs
7
+ exactly as it did before.
8
+
9
+ It is on by default. The kill switches:
10
+
11
+ ```bash
12
+ touch ~/.claude-accounts/autoresume.off # claude pool: new launches AND running watchers
13
+ touch ~/.codex-accounts/autoresume.off # codex pool: new launches AND running watchers
14
+ export CLAUDE_MULTIACC_AUTORESUME=0 # claude launches from this environment only
15
+ export CODEX_MULTIACC_AUTORESUME=0 # codex launches from this environment only
16
+ ```
17
+
18
+ - **The file** is checked by every running watcher on each one-second tick, so it also
19
+ covers sessions that are already open. A watcher that sees the file exits and leaves
20
+ its session alone.
21
+ - **The variable** is read at launch, so it only affects launches from that environment.
22
+ - **Instance pools** keep the file in their own root: `$CLAUDE_ACCOUNTS_ROOT/autoresume.off`.
23
+ - **To turn it back on**, delete the file or unset the variable. A session whose watcher
24
+ already exited stays unsupervised; its next launch gets a new one.
25
+
26
+ ## Why nothing is ever typed into the TUI
27
+
28
+ Neither client exits at a limit. Claude Code shows "continuing automatically at <reset>"
29
+ and idles. Codex prints "You've hit your usage limit … try again at …" and waits. The
30
+ manual fix was `/exit`, `claude --resume <id>` so the pool picks another account, then
31
+ "continue", about 15 times a day.
32
+
33
+ Sending those keys to the TUI is ruled out: Claude's limit menu can **add funds** or
34
+ spend the account's one-shot `/limit-reset`. So the watcher only ever stops the process.
35
+ Once the TUI has exited, it types into the **shell** that launched it.
36
+
37
+ ## How it works
38
+
39
+ ```
40
+ zsh in a tmux pane
41
+ └─ claude (shim) ─ normal selection ─ gates pass? state + watcher ─ exec real claude (same pid)
42
+ └─ python3 lib/autoresume.py watch (detached)
43
+ watcher: tail this pid's transcript (codex: rollout) → classify the error → probe the pool
44
+ → write a single-use relaunch file → SIGTERM the TUI → wait for the shell prompt
45
+ → type " CLAUDE_MULTIACC_AR=<token>:<sid> /path/to/bin/claude" + Enter into the pane
46
+ shell: runs the shim again → token → marker on the old account → normal selection minus
47
+ the accounts this chain left → exec … --resume <sid> "<prompt>" (with a new watcher)
48
+ ```
49
+
50
+ 1. **The launch is unchanged.** The shim selects exactly as before. It then `exec`s the
51
+ real binary with the same pid, stdin/stdout and exit-code passthrough.
52
+
53
+ Just before the exec, it writes a small state file to `<pool>/tmp/autoresume/`: the
54
+ pid, account, cwd, tmux pane and original argv. It then starts the watcher fully
55
+ detached.
56
+
57
+ Nothing on this path waits, runs python in the foreground or touches the network (a
58
+ relaunch adds one short trust step, see below). A failed check means no watcher, never
59
+ a failed launch.
60
+ 2. **The watcher reads what the client already writes.**
61
+ - **Claude:** `<acct>/sessions/<pid>.json` names the session. The watcher re-reads it
62
+ every tick, so it follows `/clear` and `/resume` inside the TUI. The transcript
63
+ under `projects/` records every API error.
64
+ - **Codex:** the rollout under `sessions/` is found in one of three ways, in order:
65
+ - the `resume` id;
66
+ - the native process's open files;
67
+ - only when the open files cannot be read: the newest rollout started since launch
68
+ in the same cwd, used only when exactly one matches.
69
+
70
+ In both cases:
71
+ - Only records written after this launch count, so an old rejection in a resumed
72
+ transcript never fires.
73
+ - Subagent (sidechain) records are ignored.
74
+ 3. **It waits for the error to settle.**
75
+ - **Grace period:** a verdict has to stand for 5 s.
76
+ - **Cancellation:** a message you type cancels it. So does the client's own
77
+ auto-continue producing a normal reply. For codex, a new user message,
78
+ `task_started` or `turn_aborted` cancels it.
79
+ - **Transient errors** wait longer: 30 s, then 60 s, then 120 s on repeats.
80
+ - **Model-limit and transient errors** also wait until no subagent transcript has
81
+ changed for 20 s, for up to 15 min. That background work may be healthy, or running
82
+ on another model.
83
+ 4. **It asks before it acts.** The watcher runs the shim in *probe mode*
84
+ (`CLAUDE_MULTIACC_AR_PROBE=1`). This is the same candidate loop and the same argv the
85
+ relaunch will use. It prints `pick=acct-NN tier=…` and never execs.
86
+ - **Limit, login and refusal errors** probe with the current account excluded. They
87
+ rotate only when the probe finds an **unlimited account other than the current
88
+ one**.
89
+ - **Otherwise the watcher holds.** Nothing is stopped, and Claude's own "continuing
90
+ automatically" keeps running. The probe repeats every 60 s for as long as the error
91
+ stands.
92
+ - **Transient errors** may continue on the same account, or on an all-limited
93
+ fallback that still serves.
94
+ 5. **Stop, then type.**
95
+ 1. Before stopping anything, the watcher checks that:
96
+ - the pane's process is still the shell that launched the session, and that shell
97
+ is one the typed line works in (zsh, bash, sh, dash, ksh);
98
+ - the client is in the foreground, not suspended, and leads its own process group
99
+ (a job of an interactive shell);
100
+ - no other live claude holds the same session;
101
+ - the pane is not in `synchronize-panes`, and not in copy-mode after a `cancel`.
102
+
103
+ If any check fails, it gives up and the session keeps running.
104
+ 2. It writes `r-<token>.relaunch` and `r-<token>.argv`.
105
+ 3. It sends SIGTERM, then SIGKILL after 10 s to whatever is left of the client and the
106
+ descendants it recorded just before — only processes still in the client's own
107
+ process group, never the launching shell, and none at all if the recorded tree is
108
+ implausibly large (> 64). The shim's detached `limits` refresh is left alone.
109
+ 4. It waits up to 15 s until those processes are gone and the pane has shown the shell
110
+ on two readings. If that does not happen, it removes the files and gives up. A
111
+ give-up after the stop logs `stopped=1 resume="claude --resume <sid>"` and shows the
112
+ same command in a tmux message that stays until a key is pressed.
113
+ 5. It sends the keys:
114
+ - `send-keys -R` resets the terminal modes, which codex leaves dirty.
115
+ - `C-u` discards anything typed at the prompt.
116
+ - The relaunch line, then Enter.
117
+ - It waits up to 10 s for the shim to consume the token; if the line landed
118
+ somewhere else (a `read` after the session, say), it gives up as above.
119
+
120
+ The line is ` CLAUDE_MULTIACC_AR=<token>:<sid> [CLAUDE_ACCOUNTS_ROOT=<pool>] <shim>`
121
+ (codex: `CODEX_MULTIACC_AR`, `CODEX_ACCOUNTS_ROOT`).
122
+ - The leading space keeps it out of history where `HIST_IGNORE_SPACE` /
123
+ `HISTCONTROL=ignorespace` is set.
124
+ - `<shim>` is the full path of the shim that launched the session. It skips your
125
+ `claude` alias, and the relaunch reaches the same install whatever `PATH` says. The
126
+ original flags, alias-expanded ones included, are replayed from the saved argv.
127
+ - The pool root is typed only when the session did not use the default one
128
+ (`~/.claude-accounts` / `~/.codex-accounts`).
129
+ - The session id rides along with the token so a relaunch that cannot use its token
130
+ can still name the session.
131
+ 6. **The relaunched shim does the bookkeeping.**
132
+ 1. It consumes the token. A token is single-use and valid for 10 minutes.
133
+ 2. It writes the old account's marker with the pool's existing writers (see the tables
134
+ below).
135
+ 3. It `cd`s to the session's directory.
136
+ 4. It runs **normal selection**, with one addition: the accounts this chain of
137
+ relaunches left are skipped until their own reset. The selection policy is
138
+ otherwise untouched.
139
+ 5. **claude:** it marks the directory trusted in the new account's `.claude.json`
140
+ (`projects[<dir>].hasTrustDialogAccepted`). Claude asks "Is this a project you
141
+ trust?" in a directory an account has never opened, even with
142
+ `--dangerously-skip-permissions`, and nobody is there to answer.
143
+ 6. The new pick gets a watcher of its own.
144
+
145
+ A token that is missing, malformed or older than 10 minutes starts nothing. The shim
146
+ prints `claude-multiacc: auto-resume could not continue automatically (the resume token
147
+ expired). Resume with: claude --resume <sid>` (codex: `codex resume <sid>`; the hint is
148
+ left out when the session id is unknown) and exits 2. The typed line has no argv of its
149
+ own, so carrying on would open a fresh session in place of the stopped one.
150
+
151
+ ### The relaunched command
152
+
153
+ The relaunch starts from the original argv:
154
+
155
+ - The old session selectors (`-c`, `--continue`, `--resume <id>`, codex's
156
+ `resume <id>|--last`) and the original prompt are removed.
157
+ - `--resume <sid> "<prompt>"` is appended. For codex the command becomes
158
+ `resume <options> <sid> "<prompt>"`.
159
+ - An explicit `--model` from the original launch is kept. No model is added otherwise.
160
+
161
+ The prompt is submitted automatically:
162
+
163
+ > (claude-multiacc auto-resume) This session was restarted automatically on another
164
+ > account because the previous account hit its usage limit. Continue the task from where
165
+ > you left off; the user has not sent a new message. Anything that was running in the
166
+ > background before the restart was stopped, so re-check it before relying on it, and do
167
+ > not repeat work that is already done.
168
+
169
+ - **The reason** follows the class.
170
+ - **Transient and crash restarts** drop "on another account".
171
+ - **To replace the whole text**, set `CLAUDE_MULTIACC_AUTORESUME_PROMPT` or
172
+ `CODEX_MULTIACC_AUTORESUME_PROMPT`.
173
+
174
+ ## What counts as what
175
+
176
+ **Claude**, from the transcript's API-error records:
177
+
178
+ | Class | Error record | Old account | Avoided for |
179
+ | --- | --- | --- | --- |
180
+ | quota | `rate_limit`, `quotaLimits.status: rejected`, `rateLimitType` `five_hour` / `seven_day` | `.limited` `client:<type>` until `resetsAt`, the marker the transcript scan writes | until that reset |
181
+ | model | `rate_limit` for any other limit type (a per-model weekly), "reached your … limit" / "switch to another model", `model_requires_usage_credits` | nothing: the limit covers one model | 5 h |
182
+ | auth | `authentication_failed` | `.expired`, the shim's client-reported auth park | 1 h |
183
+ | blocked | `oauth_org_not_allowed`, `account_on_hold`, `billing_error`, `verification_required` | nothing: one transcript record only makes this chain avoid it | 6 h |
184
+ | transient | `overloaded`, `server_error`, `unknown`, any other `rate_limit` | nothing | none, so the same account may continue |
185
+ | crash | the process ended on its own after ≥ 60 s, leaving its session registry behind | nothing | none, so normal selection applies |
186
+ | never | anything else (`invalid_request`, `max_output_tokens`, `model_not_found`, …) | nothing | not restarted; logged once |
187
+
188
+ **Codex**, from `task_complete` events' `error.codex_error_info`:
189
+
190
+ | Class | `codex_error_info` | Old account | Avoided for |
191
+ | --- | --- | --- | --- |
192
+ | quota | `usage_limit_exceeded`, `rate_limit_exceeded` | a 10-minute `error-cooldown` `.limited`, the same as `codex exec` auto-retry | until the limit resets (see below) |
193
+ | auth | `unauthorized` | soft `.expired` (`reason=auth-error`) that lifts after 1 h | 1 h |
194
+ | transient | `server_overloaded`, `internal_server_error`, `http_connection_failed`, `response_stream_connection_failed`, `response_stream_disconnected`, `response_too_many_failed_attempts` | nothing | none |
195
+ | never | anything else | nothing | not restarted |
196
+
197
+ For a codex quota error, the reset time comes from the first of these that is available:
198
+
199
+ 1. the last rate-limit snapshot in the rollout;
200
+ 2. the "try again at …" text;
201
+ 3. one hour from now.
202
+
203
+ Codex differs from claude in two ways:
204
+
205
+ - **A codex quota error never writes a sticky weekly `client:7d` marker.** Codex
206
+ `limits.json` is not shared between machines, so a peer Mac can redeem a reset credit in
207
+ the seconds between the error and the marker. A weekly marker would then strand a
208
+ refilled account for days. `usage_limit_exceeded` also comes from model-scoped limits.
209
+ The five-minute telemetry pass marks the account properly.
210
+ - **Codex has no crash class.** It leaves nothing behind that tells a crash from a normal
211
+ exit.
212
+
213
+ **Budgets.** A chain is a session and all of its relaunches. Each chain is allowed:
214
+
215
+ - at most 20 relaunches;
216
+ - 8 rotations per hour;
217
+ - 3 transient restarts per hour;
218
+ - 2 crash restarts per 10 minutes.
219
+
220
+ Over budget, the watcher leaves the session alone.
221
+
222
+ ## What gets a watcher
223
+
224
+ A launch gets a watcher only when all of these hold. Otherwise it is exactly today's
225
+ launch.
226
+
227
+ - **Auto-resume is on:** `*_MULTIACC_AUTORESUME` is not `0` (`false`, `no` and `off`
228
+ also turn it off), and there is no `autoresume.off`.
229
+ - **Stdin and stdout are both terminals.**
230
+ - **`$TMUX` and `$TMUX_PANE` are set.** This is the pane of the shell that launched it.
231
+ - **The argv is one the relaunch can rebuild.**
232
+ - **claude:** `--dangerously-skip-permissions`, `--allow-dangerously-skip-permissions`,
233
+ `-c` / `--continue`, `-r` / `--resume <uuid>` (or `--resume=<uuid>`), `--model <m>`,
234
+ `--effort <e>`, `--permission-mode <m>`, and at most one prompt argument that
235
+ contains a space.
236
+ - **codex:** `--dangerously-bypass-approvals-and-sandbox`, `--yolo`, `-m` / `--model <m>`,
237
+ `resume <uuid>|--last`, and at most one prompt that contains a space.
238
+ - **Anything else means no watcher.** That includes `-p` (for codex, `-p` is
239
+ `--profile`), a subcommand, `--fork-session` and any single-word argument, which
240
+ could be a subcommand.
241
+ - **The shim's path and the pool root need no shell quoting.** Both use only letters,
242
+ digits and `/._+-`, because the relaunch line is typed into a shell as it is.
243
+ - **A usable `python3` is available,** along with the addon's `lib/autoresume.py`.
244
+ - `CLAUDE_MULTIACC_PYTHON` / `CODEX_MULTIACC_PYTHON` names a specific interpreter.
245
+ - On macOS, `/usr/bin/python3` counts only when the Command Line Tools are installed.
246
+ Without them it is a stub that opens an install dialog.
247
+ - **The launch reaches the normal selected-account exec.** These get no watcher:
248
+ - pinned runs (`CLAUDE_ACCOUNT` / `CODEX_ACCOUNT`);
249
+ - passthrough (`CLAUDE_CONFIG_DIR`, `CODEX_HOME`, `*_MULTIACC_DISABLE=1`);
250
+ - `-p` / `codex exec` runs, which have [auto-retry](../README.md) instead;
251
+ - the "nothing usable" stock fallback.
252
+
253
+ ## Logs and diagnosis
254
+
255
+ Every step appends a line to the pool's `selection.log` in the form
256
+ `<UTC> autoresume <event> k=v …`. These lines never contain transcript text, prompts or
257
+ tokens.
258
+
259
+ | Event | Written by | Meaning |
260
+ | --- | --- | --- |
261
+ | `watch` | watcher | supervision started for a pid |
262
+ | `detect` | watcher | a classified error is pending |
263
+ | `never` | watcher | an error auto-resume does not handle; ignored |
264
+ | `hold` | watcher | no other account has headroom, so nothing is stopped |
265
+ | `switch` | watcher | stopped the session and typed the relaunch (`from=`, `class=`, `sid=`, `depth=`) |
266
+ | `crash` | watcher | claude ended unexpectedly; relaunching |
267
+ | `giveup` | watcher | could not relaunch safely (`reason=` pane, shell, pgrp, holder, tree, relaunch, …); with `stopped=1` the session was stopped and `resume=` names the command to run |
268
+ | `relaunch` | the relaunched shim | consumed the token (`chain=`, `from=`, `class=`, `depth=`) |
269
+
270
+ Around these lines:
271
+
272
+ - The new pick logs its usual selection line.
273
+ - The old account's marker logs its usual `LIMITED` or `parked` line.
274
+ - The second field is always the word `autoresume`. `claude-accounts status` and
275
+ `codex-accounts status` read the second field of every line as the picked account, so
276
+ an account id there would report picks that never happened.
277
+
278
+ **Debug log.** Set `CLAUDE_MULTIACC_AUTORESUME_DEBUG=1` (codex:
279
+ `CODEX_MULTIACC_AUTORESUME_DEBUG=1`) at launch, and that session's watcher writes a
280
+ verbose `<pool>/tmp/autoresume/<pid>.log`. Files in that directory are pruned after two
281
+ days.
282
+
283
+ **Probe.** To see what a relaunch would pick right now, without launching anything:
284
+
285
+ ```bash
286
+ CLAUDE_MULTIACC_AR_PROBE=1 claude --dangerously-skip-permissions
287
+ # pick=acct-07 tier=eligible
288
+ CLAUDE_MULTIACC_AR_PROBE=1 CLAUDE_MULTIACC_AR_AVOID=acct-07:1790000000 claude
289
+ ```
290
+
291
+ - **`tier`** is one of:
292
+ - `eligible`: an unlimited account;
293
+ - `soft`: only the all-limited fallback;
294
+ - `hard`: only exhausted accounts are left;
295
+ - `none`: nothing is usable; exit 3.
296
+ - **What the probe runs:** the same candidate loop as a launch, including marker upkeep
297
+ and its log lines, stopping before exec.
298
+ - **Environment:** run it with no `CLAUDE_CONFIG_DIR`, `CLAUDE_ACCOUNT` or
299
+ `CLAUDE_MULTIACC_DISABLE` set. Those pass through or pin before selection is reached.
300
+
301
+ ## Limitations
302
+
303
+ - **tmux only.** Outside tmux there is no safe place to type the relaunch, so those
304
+ sessions run unsupervised, as before.
305
+ - **Background work stops.** Subagents, background shells and monitors in the stopped
306
+ session end with it, and the continuation prompt tells the model to re-check them. For
307
+ model-limit and transient errors, the watcher first waits up to 15 min for subagent
308
+ transcripts to go quiet.
309
+ - **Only the pane's own shell is typed into.** The relaunch goes only to the shell that
310
+ launched the session. A session started from a nested shell or a script, or with
311
+ `exec claude`, gets a watcher that gives up instead of relaunching.
312
+ - **Typeahead is discarded.** Text sitting at the shell prompt when the relaunch is typed
313
+ is cleared.
314
+ - **Codex goals do not follow yet.** Codex keeps `/goal` state per account
315
+ (`goals_1.sqlite`), so set the goal again after a codex rotation. Claude restores a goal
316
+ from the transcript on resume.
317
+ - **Out of scope for this version:**
318
+ - cross-provider continuation;
319
+ - proactive switching before a limit is hit;
320
+ - an agent that simply ends its turn early, which is not an error; use `/goal` for
321
+ that.
package/docs/CODEX.md CHANGED
@@ -9,6 +9,7 @@ Codex uses the same machinery as the [Claude pool](../README.md), with these tra
9
9
  | `CLAUDE_CONFIG_DIR` per-account dirs | `CODEX_HOME` per-account dirs |
10
10
  | `.credentials.json` / macOS Keychain item (OAuth, machine-local) | `auth.json` (ChatGPT OAuth, machine-local) |
11
11
  | `claude -p` auto-retry | `codex exec` auto-retry |
12
+ | interactive `claude` auto-resume in tmux | interactive `codex` auto-resume in tmux |
12
13
  | Anthropic OAuth usage and Fable buckets | Codex usage endpoint and per-model buckets |
13
14
  | `CLAUDE_*` pool controls | equivalent `CODEX_*` controls |
14
15
 
@@ -17,7 +18,8 @@ then the 30-point weekly-headroom band with random spread, ≥90% any-bucket
17
18
  exclusion, peers rotate), same marker semantics (`.limited` cooldowns, `.expired` parks with
18
19
  credential/policy scoping and soft expiry), same fail-open guarantees, same sync
19
20
  safety guards. The `codex` shim engages the buffered auto-retry only for
20
- `codex exec` runs with finite stdin, exactly like `-p` on the claude side.
21
+ `codex exec` runs with finite stdin, exactly like `-p` on the claude side. Interactive
22
+ `codex` in tmux gets [auto-resume](AUTORESUME.md) instead, like `claude`.
21
23
 
22
24
  Codex-specific notes:
23
25
 
@@ -65,6 +67,15 @@ Codex-specific notes:
65
67
  no subtraction or embedded usage summary substitutes for provider evidence.
66
68
  Zero means no available resets; missing fields mean the read was unavailable.
67
69
  Credit identifiers remain local and selection does not use these fields.
70
+ - **Auto-resume never parks a codex account for the week.** A codex usage-limit error
71
+ in an interactive session writes only the 10-minute `error-cooldown`, the same as
72
+ `codex exec` auto-retry. It never writes a sticky `client:7d` marker. Codex
73
+ `limits.json` is not shared between machines, so a peer's reset redemption can land in
74
+ the gap, and `usage_limit_exceeded` also comes from model-scoped limits. The
75
+ five-minute limits pass marks the account from real telemetry.
76
+ - Turn auto-resume off with `touch ~/.codex-accounts/autoresume.off` (running sessions
77
+ too) or `CODEX_MULTIACC_AUTORESUME=0`.
78
+ - Codex `/goal` state is per account and does not follow a rotation yet.
68
79
  - **API-key logins are rejected** — ChatGPT subscription accounts only, matching the
69
80
  addon's no-API-keys rule.
70
81
 
@@ -5,6 +5,7 @@
5
5
  ```bash
6
6
  tests/run-tests.sh # sandboxed compatibility tests, no quota
7
7
  python3 tests/test_selector.py # unified selector contract
8
+ python3 tests/test_autoresume.py # auto-resume classifier/relaunch unit tests (also run by run-tests.sh)
8
9
  npm run test:commands # packed npm commands; run npm install --ignore-scripts first
9
10
  claude-accounts verify # real matrix: `claude -p "reply OK"` per authed account
10
11
  claude-accounts verify --quick # auth presence/expiry only, no inference
@@ -49,6 +50,12 @@ repointed via `CLAUDE_BIN=/usr/local/bin/claude` and restarted healthy.
49
50
  before direct, TUI, or `--resume` work sees the 401. Run `claude-accounts verify` to
50
51
  check every token immediately; `claude-accounts expired` reports token-only accounts
51
52
  as `UNVERIFIED` until that proof exists.
53
+ - **A session restarted itself, or did not** — that is [auto-resume](AUTORESUME.md).
54
+ `grep autoresume ~/.claude-accounts/selection.log` shows each step: `hold` means no
55
+ other account had headroom, and `giveup` gives the reason nothing was relaunched. A
56
+ launch outside tmux, with piped stdio, or with flags the relaunch cannot rebuild gets no
57
+ watcher at all. `touch ~/.claude-accounts/autoresume.off` turns it off, running
58
+ sessions included (codex: `~/.codex-accounts/autoresume.off`).
52
59
  - **Everything marked limited** — the shim still runs: the still-serving limited accounts
53
60
  go through the same two cuts (session gate, then strict best-weekly) and one is handed
54
61
  out anyway; check `selection.log` for `all-limited fallback=` lines.
@@ -70,7 +77,8 @@ repointed via `CLAUDE_BIN=/usr/local/bin/claude` and restarted healthy.
70
77
 
71
78
  - Tokens and credentials live as 0600 files under `~/.claude-accounts` (and
72
79
  `/root/.claude-accounts` on the server). Server compromise = account access.
73
- - `selection.log` records timestamps/account/cwd only — never prompt text.
80
+ - `selection.log` records timestamps, accounts and cwd, and for auto-resume also
81
+ session ids and error classes. It never records prompt or transcript text.
74
82
  - Rotating multiple subscriptions to spread usage may be flagged by anti-abuse systems;
75
83
  accounts can be banned for limit circumvention. Known and accepted by the operator.
76
84
  - The usage endpoint is the internal one `/usage` consumes; if it changes shape, limit