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.
- package/README.md +60 -2
- package/bin/claude +414 -5
- package/bin/claude-accounts +36 -11
- package/bin/codex +406 -4
- package/docs/ACCOUNT_OPERATIONS.md +7 -1
- package/docs/AUTORESUME.md +321 -0
- package/docs/CODEX.md +12 -1
- package/docs/VERIFICATION.md +9 -1
- package/lib/__pycache__/audit.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
- package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
- package/lib/__pycache__/mcp_registry.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
- package/lib/__pycache__/shim_path.cpython-312.pyc +0 -0
- package/lib/autoresume.py +2271 -0
- package/lib/common.sh +24 -1
- package/lib/keychain.py +274 -59
- package/package.json +1 -1
- package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_claude_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
- package/tests/fake_security.sh +90 -0
- package/tests/run-tests.sh +1178 -67
- package/tests/test_autoresume.py +2565 -0
- 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
|
|
package/docs/VERIFICATION.md
CHANGED
|
@@ -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
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|