claude-multiacc 2.0.58 → 2.0.60

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 (151) hide show
  1. package/README.md +125 -4
  2. package/bin/claude +389 -94
  3. package/bin/claude-accounts +276 -42
  4. package/bin/codex +69 -5
  5. package/bin/codex-accounts +33 -4
  6. package/docs/ACCOUNT_OPERATIONS.md +62 -2
  7. package/docs/AUTORESUME-HANDOFF.md +28 -3
  8. package/docs/AUTORESUME.md +275 -18
  9. package/docs/CODEX.md +1 -0
  10. package/docs/INSTALLATION.md +7 -1
  11. package/docs/VERIFICATION.md +4 -0
  12. package/lib/__pycache__/audit.cpython-312.pyc +0 -0
  13. package/lib/__pycache__/autoresume.cpython-312.pyc +0 -0
  14. package/lib/__pycache__/autoresume_cross.cpython-312.pyc +0 -0
  15. package/lib/__pycache__/autoresume_cross_dispatch.cpython-312.pyc +0 -0
  16. package/lib/__pycache__/autoresume_cross_receipt.cpython-312.pyc +0 -0
  17. package/lib/__pycache__/autoresume_handoff.cpython-312.pyc +0 -0
  18. package/lib/__pycache__/autoresume_probe.cpython-312.pyc +0 -0
  19. package/lib/__pycache__/autoresume_receipt.cpython-312.pyc +0 -0
  20. package/lib/__pycache__/claude_client_error.cpython-312.pyc +0 -0
  21. package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
  22. package/lib/__pycache__/claude_token_identity.cpython-312.pyc +0 -0
  23. package/lib/__pycache__/codex_autoresume_mode.cpython-312.pyc +0 -0
  24. package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
  25. package/lib/__pycache__/codex_credit_controls.cpython-312.pyc +0 -0
  26. package/lib/__pycache__/codex_credit_local_scope.cpython-312.pyc +0 -0
  27. package/lib/__pycache__/codex_credit_selection.cpython-312.pyc +0 -0
  28. package/lib/__pycache__/codex_credits.cpython-312.pyc +0 -0
  29. package/lib/__pycache__/codex_limits_lock.cpython-312.pyc +0 -0
  30. package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
  31. package/lib/__pycache__/codex_trust.cpython-312.pyc +0 -0
  32. package/lib/__pycache__/codex_usage_fetch.cpython-312.pyc +0 -0
  33. package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
  34. package/lib/__pycache__/mcp_oauth.cpython-312.pyc +0 -0
  35. package/lib/__pycache__/mcp_registry.cpython-312.pyc +0 -0
  36. package/lib/__pycache__/model_catalog.cpython-312.pyc +0 -0
  37. package/lib/__pycache__/model_match.cpython-312.pyc +0 -0
  38. package/lib/__pycache__/report.cpython-312.pyc +0 -0
  39. package/lib/__pycache__/selector_credit_scope.cpython-312.pyc +0 -0
  40. package/lib/__pycache__/selector_credits.cpython-312.pyc +0 -0
  41. package/lib/__pycache__/selector_funding.cpython-312.pyc +0 -0
  42. package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
  43. package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
  44. package/lib/__pycache__/session_settings.cpython-312.pyc +0 -0
  45. package/lib/__pycache__/shim_path.cpython-312.pyc +0 -0
  46. package/lib/audit.py +88 -22
  47. package/lib/autoresume.py +1197 -82
  48. package/lib/autoresume_cross.py +3 -1
  49. package/lib/autoresume_handoff.py +81 -9
  50. package/lib/autoresume_probe.py +2 -2
  51. package/lib/claude_client_error.py +9 -4
  52. package/lib/codex_trust.py +263 -0
  53. package/lib/common.sh +146 -3
  54. package/lib/credential.py +5 -2
  55. package/lib/install_actions.sh +19 -0
  56. package/lib/keychain.py +180 -36
  57. package/lib/mcp_oauth.py +1748 -0
  58. package/lib/mcp_registry.py +230 -8
  59. package/lib/model_catalog.json +404 -0
  60. package/lib/model_catalog.py +201 -0
  61. package/lib/model_match.py +104 -0
  62. package/lib/report.py +8 -5
  63. package/lib/session_settings.py +123 -0
  64. package/package.json +2 -2
  65. package/tests/__pycache__/autoresume_cross_receipt_support.cpython-312.pyc +0 -0
  66. package/tests/__pycache__/autoresume_cross_tmux_environment.cpython-312.pyc +0 -0
  67. package/tests/__pycache__/autoresume_cross_tmux_support.cpython-312.pyc +0 -0
  68. package/tests/__pycache__/autoresume_cross_unit_support.cpython-312.pyc +0 -0
  69. package/tests/__pycache__/autoresume_tmux_support.cpython-312.pyc +0 -0
  70. package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
  71. package/tests/__pycache__/test_autoresume.cpython-312.pyc +0 -0
  72. package/tests/__pycache__/test_autoresume_catalog.cpython-312.pyc +0 -0
  73. package/tests/__pycache__/test_autoresume_catalog_cli.cpython-312.pyc +0 -0
  74. package/tests/__pycache__/test_autoresume_codex_records.cpython-312.pyc +0 -0
  75. package/tests/__pycache__/test_autoresume_codex_trust.cpython-312.pyc +0 -0
  76. package/tests/__pycache__/test_autoresume_cross_claude_holders.cpython-312.pyc +0 -0
  77. package/tests/__pycache__/test_autoresume_cross_guards.cpython-312.pyc +0 -0
  78. package/tests/__pycache__/test_autoresume_cross_holders.cpython-312.pyc +0 -0
  79. package/tests/__pycache__/test_autoresume_cross_origin.cpython-312.pyc +0 -0
  80. package/tests/__pycache__/test_autoresume_cross_policy.cpython-312.pyc +0 -0
  81. package/tests/__pycache__/test_autoresume_cross_receipt.cpython-312.pyc +0 -0
  82. package/tests/__pycache__/test_autoresume_cross_tmux.cpython-312.pyc +0 -0
  83. package/tests/__pycache__/test_autoresume_cross_tmux_races.cpython-312.pyc +0 -0
  84. package/tests/__pycache__/test_autoresume_cross_tokens.cpython-312.pyc +0 -0
  85. package/tests/__pycache__/test_autoresume_cursor.cpython-312.pyc +0 -0
  86. package/tests/__pycache__/test_autoresume_fallback.cpython-312.pyc +0 -0
  87. package/tests/__pycache__/test_autoresume_handoff_argv.cpython-312.pyc +0 -0
  88. package/tests/__pycache__/test_autoresume_handoff_history.cpython-312.pyc +0 -0
  89. package/tests/__pycache__/test_autoresume_handoff_origins.cpython-312.pyc +0 -0
  90. package/tests/__pycache__/test_autoresume_memory.cpython-312.pyc +0 -0
  91. package/tests/__pycache__/test_autoresume_memory_tmux.cpython-312.pyc +0 -0
  92. package/tests/__pycache__/test_autoresume_mode.cpython-312.pyc +0 -0
  93. package/tests/__pycache__/test_autoresume_probe.cpython-312.pyc +0 -0
  94. package/tests/__pycache__/test_autoresume_receipt.cpython-312.pyc +0 -0
  95. package/tests/__pycache__/test_autoresume_relaunch_settings.cpython-312.pyc +0 -0
  96. package/tests/__pycache__/test_autoresume_settings.cpython-312.pyc +0 -0
  97. package/tests/__pycache__/test_autoresume_sidechain.cpython-312.pyc +0 -0
  98. package/tests/__pycache__/test_autoresume_sidechain_tmux.cpython-312.pyc +0 -0
  99. package/tests/__pycache__/test_autoresume_tmux.cpython-312.pyc +0 -0
  100. package/tests/__pycache__/test_autoresume_tmux_modes.cpython-312.pyc +0 -0
  101. package/tests/__pycache__/test_autoresume_trust_tmux.cpython-312.pyc +0 -0
  102. package/tests/__pycache__/test_claude_reset.cpython-312.pyc +0 -0
  103. package/tests/__pycache__/test_claude_token_identity.cpython-312.pyc +0 -0
  104. package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
  105. package/tests/__pycache__/test_codex_reset_credits.cpython-312.pyc +0 -0
  106. package/tests/__pycache__/test_codex_reset_credits_polling.cpython-312.pyc +0 -0
  107. package/tests/__pycache__/test_codex_reset_credits_report.cpython-312.pyc +0 -0
  108. package/tests/__pycache__/test_codex_reset_fetch.cpython-312.pyc +0 -0
  109. package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
  110. package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
  111. package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
  112. package/tests/__pycache__/test_selector.cpython-312.pyc +0 -0
  113. package/tests/__pycache__/test_selector_cached_credits.cpython-312.pyc +0 -0
  114. package/tests/__pycache__/test_selector_credit_scope.cpython-312.pyc +0 -0
  115. package/tests/__pycache__/test_selector_credit_shim.cpython-312.pyc +0 -0
  116. package/tests/__pycache__/test_selector_credits.cpython-312.pyc +0 -0
  117. package/tests/__pycache__/test_selector_spread.cpython-312.pyc +0 -0
  118. package/tests/__pycache__/test_token_identity_ceremony.cpython-312.pyc +0 -0
  119. package/tests/__pycache__/test_token_identity_integration.cpython-312.pyc +0 -0
  120. package/tests/__pycache__/test_token_identity_retry.cpython-312.pyc +0 -0
  121. package/tests/__pycache__/test_token_identity_sync.cpython-312.pyc +0 -0
  122. package/tests/fake_codex_tui.py +25 -0
  123. package/tests/fake_security.sh +24 -2
  124. package/tests/fixtures/cross_provider_cli.py +109 -0
  125. package/tests/run-tests.sh +1171 -23
  126. package/tests/test_autoresume.py +215 -8
  127. package/tests/test_autoresume_catalog.py +226 -0
  128. package/tests/test_autoresume_catalog_cli.py +185 -0
  129. package/tests/test_autoresume_codex_records.py +16 -0
  130. package/tests/test_autoresume_codex_trust.py +247 -0
  131. package/tests/test_autoresume_cross_guards.py +28 -0
  132. package/tests/test_autoresume_cross_tmux.py +27 -2
  133. package/tests/test_autoresume_cross_tokens.py +32 -2
  134. package/tests/test_autoresume_handoff_argv.py +120 -16
  135. package/tests/test_autoresume_handoff_origins.py +14 -0
  136. package/tests/test_autoresume_memory.py +1141 -0
  137. package/tests/test_autoresume_memory_tmux.py +94 -0
  138. package/tests/test_autoresume_probe.py +2 -1
  139. package/tests/test_autoresume_relaunch_settings.py +317 -0
  140. package/tests/test_autoresume_settings.py +135 -0
  141. package/tests/test_autoresume_sidechain.py +1518 -0
  142. package/tests/test_autoresume_sidechain_tmux.py +79 -0
  143. package/tests/test_autoresume_trust_tmux.py +63 -0
  144. package/tests/test_claude_client_error.py +10 -7
  145. package/tests/test_claude_org_block.py +1 -0
  146. package/tests/test_claude_pool_prepare.py +4 -1
  147. package/tests/test_claude_reset.py +3 -0
  148. package/tests/test_keychain_nologin.py +340 -0
  149. package/tests/test_mcp_oauth.py +1414 -0
  150. package/tests/test_mcp_registry.py +234 -3
  151. package/tests/test_packaged_commands.py +41 -0
@@ -1,7 +1,8 @@
1
1
  # Auto-resume (interactive sessions in tmux)
2
2
 
3
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
4
+ API error or (claude only) a crash or [critical memory usage](#claude-critical-memory) is
5
+ restarted automatically on another pooled account
5
6
  with headroom. Within the same provider it keeps the **same session id, directory and
6
7
  flags**, and you press no keys. When a quota recovery has no usable account left in that
7
8
  provider, it can continue in the other provider using the source conversation's history.
@@ -69,7 +70,12 @@ shell: runs the shim again → token → marker on the old account → normal
69
70
  2. **The watcher reads what the client already writes.**
70
71
  - **Claude:** `<acct>/sessions/<pid>.json` names the session. The watcher re-reads it
71
72
  every tick, so it follows `/clear` and `/resume` inside the TUI. The transcript
72
- under `projects/` records every API error.
73
+ under `projects/` records every API error of the main thread. Subagents and workflow
74
+ agents keep their own transcripts beside it; the watcher follows those too, but only
75
+ for the account's own verdicts (see
76
+ [subagent transcripts](#claude-subagent-and-workflow-transcripts)). The client's
77
+ critical-memory warning reaches no transcript, so for that one the watcher reads its
78
+ own pane (see [critical memory](#claude-critical-memory)).
73
79
  - **Codex:** the rollout under `sessions/` is found in one of three ways, in order:
74
80
  - the `resume` id;
75
81
  - the native process's open files;
@@ -81,16 +87,23 @@ shell: runs the shim again → token → marker on the old account → normal
81
87
  transcript never fires. An automatic Codex restart also carries the previous
82
88
  rollout's inode and byte offset: the new watcher starts after those bytes, so a
83
89
  quota event from the same second cannot trigger another rotation.
84
- - Subagent (sidechain) records are ignored.
90
+ - Subagent (sidechain) records in the main transcript are ignored.
85
91
  3. **It waits for the error to settle.**
86
92
  - **Grace period:** a verdict has to stand for 5 s.
87
93
  - **Cancellation:** a message you type cancels it. So does the client's own
88
- auto-continue producing a normal reply. For codex, a new user message,
89
- `task_started` or `turn_aborted` cancels it.
94
+ auto-continue producing a normal reply. A slash command that only shows or
95
+ configures the session (`/model`, `/effort`, `/usage`, `/status`, `/mcp` …) cancels
96
+ only a model-limit or transient verdict: `/model` is how you handle one model's
97
+ limit, but a usage-limited, logged-out or refused account is just as stuck after it.
98
+ Any other command counts as typing: `/login` and `/limit-reset` are how you fix the
99
+ account itself. A verdict a subagent recorded is also cancelled by a newer reply in
100
+ any subagent transcript. For codex, a new user message, `task_started` or
101
+ `turn_aborted` cancels it.
90
102
  - **Transient errors** wait longer: 30 s, then 60 s, then 120 s on repeats.
91
103
  - **Model-limit and transient errors** also wait until no subagent transcript has
92
104
  changed for 20 s, for up to 15 min. That background work may be healthy, or running
93
- on another model.
105
+ on another model. An agent meeting the account's own limit meanwhile ends the wait:
106
+ its verdict outranks the main thread's (see [subagents](#claude-subagent-and-workflow-transcripts)).
94
107
  4. **It asks before it acts.** The watcher runs the shim in *probe mode*
95
108
  (`CLAUDE_MULTIACC_AR_PROBE=1`). This is the same candidate loop and the same argv the
96
109
  relaunch will use. It prints `pick=acct-NN tier=…` and never execs.
@@ -159,6 +172,16 @@ shell: runs the shim again → token → marker on the old account → normal
159
172
  (`projects[<dir>].hasTrustDialogAccepted`). Claude asks "Is this a project you
160
173
  trust?" in a directory an account has never opened, even with
161
174
  `--dangerously-skip-permissions`, and nobody is there to answer.
175
+ **codex:** it saves `projects[<dir>].trust_level = "trusted"` in the selected
176
+ account's `config.toml` before startup, so the **Folder access / Trust this
177
+ folder?** screen cannot block the continuation. This also covers a handoff from
178
+ Claude. Only a consumed automatic relaunch token enables this step; normal
179
+ launches keep their existing trust decisions. The logical, physical and on-disk
180
+ spellings of the directory are covered (Codex looks the canonical one up first), an
181
+ explicit `untrusted` answer is overridden (nobody is there to answer again), and
182
+ unrelated settings and file permissions are preserved. An unreadable, invalid or
183
+ concurrently changed config is left untouched and logs `autoresume trust-failed`;
184
+ Codex then asks on its own screen and the watcher gives up on the missing prompt.
162
185
  6. The new pick gets a watcher of its own.
163
186
  7. For Codex, the old watcher waits up to 30 seconds for the exact continuation
164
187
  prompt to appear as a new user message in the same rollout. Only then does it log
@@ -180,7 +203,8 @@ The relaunch starts from the original argv:
180
203
  `resume <id>|--last`) and the original prompt are removed.
181
204
  - `--resume <sid> "<prompt>"` is appended. For codex the command becomes
182
205
  `resume <options> <sid> "<prompt>"`.
183
- - An explicit `--model` from the original launch is kept. No model is added otherwise.
206
+ - The session's own model and effort are carried over, as described below, in place of
207
+ launch options that a `/model` or `/effort` switch made stale.
184
208
  - Codex's explicit or automatically selected `--no-daemon` is kept.
185
209
 
186
210
  The prompt is submitted automatically:
@@ -191,11 +215,45 @@ The prompt is submitted automatically:
191
215
  > may still be running. Inspect its state before relying on it or restarting it, and do
192
216
  > not duplicate active or completed work.
193
217
 
194
- - **The reason** follows the class.
195
- - **Transient and crash restarts** drop "on another account".
218
+ - **The reason** follows the class; a memory restart's names the size the banner showed.
219
+ - **Transient, crash and memory restarts** drop "on another account".
196
220
  - **To replace the whole text**, set `CLAUDE_MULTIACC_AUTORESUME_PROMPT` or
197
221
  `CODEX_MULTIACC_AUTORESUME_PROMPT`.
198
222
 
223
+ ### Model and effort
224
+
225
+ The watcher reads the model and effort from the session's own history when it decides to
226
+ relaunch, so a `/model` or `/effort` switch made after the launch is honoured:
227
+
228
+ - **Claude:** the last main-thread assistant record, never a subagent's or a meta record.
229
+ The model is its `requestedModel`, else `message.model`. The effort is its `effort`, else
230
+ `perTurnEffort`. A `/model` switch with no reply after it is not visible yet, because only
231
+ assistant records carry the model.
232
+ - **Codex:** whichever comes last of a `turn_context` (`model`, `effort`) and a
233
+ `thread_settings_applied` event for the rollout's own thread (`model`,
234
+ `reasoning_effort`). This is the rule Codex's own state database follows.
235
+
236
+ They become flags placed before the session id:
237
+
238
+ - **Claude** gets `--effort E`, because `--resume` never restores the effort. It gets no
239
+ model flag: `--resume` restores the session's model itself, and an explicit `--model`
240
+ would turn that restore off and put the shim in explicit-model mode, where its
241
+ model-scoped fallback (the Fable bucket) no longer applies. A launch `--model` is replayed
242
+ as given until the history shows the session ran on another model; then it is dropped and
243
+ `--resume` takes over. The relaunch file names that model (`model=`), so the shim pins its
244
+ fallback model only for a session that is on the limited model itself: an Opus session
245
+ relaunched onto an account whose Fable bucket is full resumes on Opus, not the fallback.
246
+ - **Codex** gets `-m M -c model_reasoning_effort=E`, always both or neither: a model or an
247
+ effort flag alone makes Codex drop the thread's other saved value. An effort the history
248
+ does not name becomes that model's default from the catalog; with no default either, the
249
+ launch's own model and effort options are dropped too, and Codex restores both from the
250
+ thread.
251
+
252
+ The [model catalog](../README.md#auto-resume) also clamps an effort to the levels that
253
+ model supports. A value the history does not record is left out, and a history that cannot
254
+ be read relaunches with the launch's own options, as before. The `switch` line records
255
+ what the relaunched client was given (`model=`, `effort=`).
256
+
199
257
  ## What counts as what
200
258
 
201
259
  **Claude**, from the transcript's API-error records:
@@ -207,10 +265,18 @@ The prompt is submitted automatically:
207
265
  | auth | `authentication_failed` | `.expired`, the shim's client-reported auth park | 1 h |
208
266
  | blocked | `oauth_org_not_allowed` | persistent `.expired` `org-blocked` park | 6 h in this chain |
209
267
  | blocked | `account_on_hold`, `billing_error`, `verification_required` | nothing: only this chain avoids it | 6 h |
210
- | transient | `overloaded`, `server_error`, `unknown`, any other `rate_limit` | nothing | none, so the same account may continue |
268
+ | transient | `overloaded`, `server_error`, `unknown`, `rateLimitType` `concurrents` (too many requests in flight at once; the account serves the next one), any other `rate_limit` | nothing | none, so the same account may continue |
211
269
  | crash | the process ended on its own after ≥ 60 s, leaving its session registry behind | nothing | none, so normal selection applies |
270
+ | memory | not a record: the client's `Critical memory usage (<size>)` banner in its own pane ([critical memory](#claude-critical-memory)) | nothing | none, so normal selection applies |
212
271
  | never | anything else (`invalid_request`, `max_output_tokens`, `model_not_found`, …) | nothing | not restarted; logged once |
213
272
 
273
+ An auth verdict parks the credential the stopped run carried: the launching shim writes
274
+ `via=token|login` into the watcher's state, and the relaunch file carries it (`via=`), so
275
+ a portable token that failed is retired as `setup-token-invalid` (its proof dropped) and a
276
+ login gets `auth-error`, even in a session the client switched the run to with `/clear`
277
+ or `/resume`, which no session-index line names. A relaunch file without `via=` (an older
278
+ watcher) falls back to the session's index line.
279
+
214
280
  An organization refusal carries `rtype=oauth_org_not_allowed` into the relaunch file,
215
281
  so the shim parks the old account even if its earlier session-index capture missed it.
216
282
  An admin must enable access, then full `claude-accounts verify` must prove it; a PASS
@@ -247,9 +313,188 @@ Codex differs from claude in two ways:
247
313
  - at most 20 relaunches;
248
314
  - 8 rotations per hour;
249
315
  - 3 transient restarts per hour;
250
- - 2 crash restarts per 10 minutes.
316
+ - 2 crash restarts per 10 minutes;
317
+ - 2 memory restarts per hour.
318
+
319
+ Over budget, the watcher leaves the session alone. Over the memory budget it gives up on
320
+ that process's memory for good (logged once) and goes on watching for everything else.
321
+
322
+ ### Claude subagent and workflow transcripts
323
+
324
+ An account limit can reach only the background agents. On 2026-10-19 two sessions sat
325
+ idle behind paused workflows: the workflow agents had recorded the five-hour limit, the
326
+ client knew it and sent no further main-thread request, and the main transcript showed
327
+ nothing to act on. So the watcher also follows the session's
328
+ `<sid>/subagents/agent-*.jsonl` (Agent tool subagents) and
329
+ `<sid>/subagents/workflows/wf_*/agent-*.jsonl` (workflow agents) beside the transcript.
330
+
331
+ - **Only the account's own verdicts count there:** quota (`five_hour` / `seven_day`
332
+ rejected), auth (`authentication_failed`) and blocked (the codes in the table above).
333
+ A subagent's model-scoped limit, transient error or any other error is its own and is
334
+ ignored, and so are its user records: they are its parent's prompts, never a human.
335
+ - **From this launch only:** a record needs a timestamp from this launch, and a
336
+ rejection one from at least a second after it. The main thread's two seconds of slack
337
+ before the launch would admit the last rejections of the process a relaunch replaced:
338
+ its workflow agents meet a limit in bursts that span seconds and run on until it is
339
+ stopped, a second or two before the relaunch. A transcript last written before the
340
+ launch is followed from its end.
341
+ - **The main thread comes first, unless the account is out.** A main-thread quota, auth
342
+ or blocked verdict stands over any subagent's. Several agents meeting the same limit make
343
+ one verdict. An account verdict, though, outranks a model-limit or transient one,
344
+ whichever thread met it first: the account serves the main thread no better, and the
345
+ model/transient verdict would wait on agents that are only being refused. On 2026-10-20 a
346
+ session sat six minutes behind its main thread's `concurrents` rejection while all its
347
+ agents met the five-hour limit. The outranked verdict waits behind it and stands
348
+ again only if that one goes without the main thread having moved on: an agent's newer
349
+ reply answers the account, or a quota reset passes. Anything the main thread does next
350
+ (typing, `/model`, a reply, another rejection) ends it.
351
+ - **The latest reset is the one marked.** Rejections of one class — main thread and
352
+ agents alike — are one verdict, marked with the latest reset any of them named and that
353
+ reset's type: a five-hour limit in one transcript and the weekly one in another keep the
354
+ account out until the weekly one lifts.
355
+ - **A limit that has reset is no verdict.** A quota rejection whose `resetsAt` has passed
356
+ is dropped when it is read, and a pending one whose reset passes while it waits (the
357
+ grace, a hold for want of room) is dropped then, never acted on; a rejection without a
358
+ `resetsAt` stands as before. This holds for the main thread too.
359
+ - **Cancellation:** a real reply to a request sent after the verdict, in the main thread
360
+ or in any subagent transcript, or a message you type. A reply counts from when its
361
+ request was sent: the `api-request` record the client writes right before each request,
362
+ which the reply names by `requestRef` (the reply's own timestamp when there is none). A
363
+ stream the account accepted before the limit can end many minutes after it, and an
364
+ agent's final answer streamed that way, the last record a paused workflow writes, must
365
+ not stand for the account serving again. A slash command that only shows or configures
366
+ the session never counts, any other one (`/login`, `/limit-reset` …) does, and a
367
+ `<synthetic>` record (the client's own words, not the API's) never does. Records older
368
+ than the verdict cancel nothing, whatever order the transcripts are read in.
369
+ - **A restarted watcher replays safely.** A watcher started onto a session launched hours
370
+ ago reads everything written since that launch. While nothing is pending it skips the
371
+ main thread's lines unparsed (its request records excepted), but the latest real reply
372
+ or typed message among them still counts: an agent's rejection older than it never
373
+ becomes a verdict. An old workflow limit cannot stop a session whose main thread has
374
+ been served since. Nor can one whose agents have been: a subagent's verdict is acted on
375
+ only once every followed transcript is read to its end, which can take a minute of
376
+ ticks for a session with hundreds of megabytes of agent transcripts.
377
+ - **It waits for the main thread's work in flight.** A main-thread verdict comes from the
378
+ main thread's own rejected request, so whatever it was doing has ended by then; an
379
+ agent's verdict can come mid-tool or mid-stream. So it is not acted on while the
380
+ main thread's newest request (`querySource` `repl_main_thread`) has nothing back yet,
381
+ while that request's reply is still streaming (its newest block has `"stop_reason":
382
+ null`, or says `tool_use` before the tool_use block is written), or while a tool the
383
+ reply started has no `tool_result` yet. Stopping the client would kill the tool's
384
+ processes with it. This is keyed by request, never by the file's order: the client
385
+ writes the request record as it sends, sometimes before the prompt that led to it and
386
+ before the previous reply's last blocks. An API error after the request, the client's
387
+ `[Request interrupted by user` record, or a finished reply ends the wait. So does a
388
+ main-thread rejection of its own, which then stands in the agent's verdict's place. The
389
+ wait is capped at `CLAUDE_MULTIACC_AR_SUBAGENT_CAP` (15 min) from when the verdict
390
+ became pending; a tool waiting for a permission nobody gives waits that long. The
391
+ watcher keeps the main transcript's newest 4 MB of lines for this and reads the file no
392
+ second time; the wait is logged once, as `hold reason=main-<request|stream|tool>`. An
393
+ idle main thread (a finished reply, queued task notices, `/model`) moves at the grace as
394
+ before. The agents' own streams and tools are not waited for: the paused workflow ends
395
+ with the session either way.
396
+ - **Everything after that is the main thread's path:** grace, budgets, probe, hold,
397
+ cross-provider continuation, the old account's marker and the `switch` line. The
398
+ `detect` line adds `src=sidechain agent=<agentId>`.
399
+ - **Bounds:** new transcripts are looked for every 5 s
400
+ (`CLAUDE_MULTIACC_AR_SUBAGENT_SCAN`), in a walk of at most 5000 entries. The 128 most
401
+ recently written are followed and share 16 MB of reading per tick; they are read only
402
+ once the main transcript is caught up. Only their API-error and request lines and each
403
+ read's latest reply are parsed, the reply found from the read's end. A workflow's
404
+ `journal.jsonl` and an agent's `*.meta.json` are not transcripts.
405
+
406
+ ### Claude critical memory
407
+
408
+ Claude Code checks its own heap every 10 s (2.1.304: `process.memoryUsage().heapUsed`). From
409
+ 2.5 GiB on it shows, in red, in the notification column at the right of the prompt's footer:
410
+
411
+ ```
412
+ ⏵⏵ bypass permissions on · 3 shells Critical memory usage (8.3GB) — restart and resume with claude --continue, or run /compact
413
+ ```
251
414
 
252
- Over budget, the watcher leaves the session alone.
415
+ It writes nothing of this to the transcript. On 2026-10-20 a session running eight
416
+ background workflows reached 8.3GB. The fix is what the banner says, so the operator's rule
417
+ is the usage-limit one: the session is restarted and resumed automatically.
418
+
419
+ - **Where it is read:** the watcher reads its own pane, read-only, with `tmux capture-pane
420
+ -p -J` on the socket and pane from its state file. It never sends a key. It reads only
421
+ while the pane is still the launching shell's and the client runs in the foreground of its
422
+ terminal (not suspended), and only for a session it can name to `--resume`.
423
+ - **What counts:** only the footer of the prompt's box. The box is found by its shape:
424
+ a rule across the pane from its first column, right under it the prompt's row (the mode
425
+ indicator in the first column, `❯`, `!` in shell mode or the agent a teammate view names,
426
+ then the client's no-break space), the draft's other rows indented past the indicator, and
427
+ the rule that closes it. The footer is the rows under that rule, down to the first blank
428
+ row. The conversation is above the box and the draft inside it, so the banner's words in a
429
+ reply, a quoted log line, a tool's output or the prompt never count. Neither do the
430
+ background tasks, listed after that blank row. A dialog drawn in the box's place (a
431
+ permission request, a plan to approve) has a top rule only, its title and body indented
432
+ under it. It opens no box, so nothing it quotes counts, and the same goes for a picker or
433
+ a question. The match is the client's
434
+ own shape: `Critical memory usage (<size>)`, with the size in the client's format (`8.3GB`,
435
+ `12GB`, `900MB`), then ` — restart and resume with claude --continue` with its em dash,
436
+ whole or cut at an ellipsis. A hyphen or an en dash after the size is not the banner. The
437
+ banner is the notification column's last line, so it ends its row: only the client's own
438
+ pills may follow it, a stashed prompt's (`› stashed`) and an active goal's
439
+ (`◎ /goal active (5m)`). The banner can be on the mode line's row or, when the pane is too
440
+ narrow, on a footer row of its own (the notification column wraps). It can also directly
441
+ follow the mode line, which is then cut short too. A row of the background-task list never
442
+ counts, even with no blank row above it: it starts with the selection pointer or two spaces,
443
+ an agent's tree connector, then the task's bullet (`◯`, `⏺` or `●` for the task viewed,
444
+ `⏸` for a paused workflow), and a task's name and description are the model's own words.
445
+ Escape sequences are ignored, and wrapped rows are joined. A banner cut before its size
446
+ closes goes unseen.
447
+ - **When:** every `CLAUDE_MULTIACC_AR_MEMORY_SCAN` seconds (30; `0` turns the memory watch
448
+ off), and never before the launch is `CLAUDE_MULTIACC_AR_MEMORY_MIN_UPTIME` old (900 s).
449
+ A resumed session can come back as heavy as it left, and this keeps it out of a restart
450
+ loop.
451
+ - **Then the main thread's path:** grace, budgets, probe, hold for want of room, the
452
+ relaunch file, the stop and the typed relaunch, with these differences:
453
+ - **Any account may take the session**, the one it is on included, exactly as after a
454
+ crash. Nothing is marked on the old account and the chain avoids nothing new: the shim
455
+ runs ordinary selection.
456
+ - **The transcript's own verdict goes first.** A usage limit, login failure or API error
457
+ pending at the same time moves the session instead, which also ends the heavy process.
458
+ - **The main thread's work in flight is waited for:** a request with nothing back, a reply
459
+ still streaming, a tool with no result (the same rules as for an agent's verdict), and a
460
+ compaction under way. `/compact` is the banner's other remedy, and the transcript shows
461
+ nothing of it until it is done, so the pane's spinner above the prompt box counts:
462
+ `Compacting conversation…`. The wait is logged once as
463
+ `hold reason=main-<request|stream|tool|compact>`. Each wait is capped at
464
+ `CLAUDE_MULTIACC_AR_MEMORY_BUSY_CAP` (600 s), since the process may not last much
465
+ longer. The cap counts from the first reading that found that work, not from the
466
+ detection. Time the verdict spent held for want of room, or behind a transcript verdict,
467
+ uses none of it, and work started later gets a full cap of its own. A main thread found
468
+ idle ends its wait. Background agents and workflows are not waited for: they run
469
+ continuously, and they end with the session either way.
470
+ - **The banner going away ends the verdict.** After `/compact` the heap can fall back under
471
+ the line. The pane is read again on each scan and once more right before the stop. A
472
+ banner that comes and goes (a heap near the line, a pane read that fails) changes only the
473
+ memory verdict: a transcript verdict pending meanwhile keeps its own probes and hold lines.
474
+ - **A check that refuses the stop drops only the memory verdict.** The checks before any
475
+ stop still apply: the pane and its shell, the client in the foreground and leading its
476
+ process group, no other client holding the session, its directory, no synchronized panes
477
+ or stuck mode, and the relaunch file written. When one refuses a usage limit, a login
478
+ failure or a crash, the watcher gives up, as always. When one refuses a memory restart,
479
+ the watcher logs `giveup reason=memory-<why> class=memory` once and drops the verdict.
480
+ It then does not read the pane for `CLAUDE_MULTIACC_AR_MEMORY_BACKOFF` seconds (600), and
481
+ keeps watching the session for everything else.
482
+ - **At most `CLAUDE_MULTIACC_AR_MEMORY_PER_HOUR` (2) memory restarts per chain per hour.**
483
+ Beyond that the watcher logs `giveup reason=memory-budget class=memory` once and leaves
484
+ that process's memory alone, but keeps watching for every other verdict.
485
+ - **The prompt says why:** "…restarted automatically because the previous process reached
486
+ critical memory usage (8.3GB). …", without "on another account".
487
+ - **A client that ignores SIGTERM** (the 8.3GB one did, for over 15 s) gets SIGKILL after
488
+ `CLAUDE_MULTIACC_AR_TERM_GRACE` (10 s), like any stop. The relaunch is typed once the
489
+ client and the processes recorded with it are gone and the shell is back. Tearing a huge
490
+ process down can take the kernel a few seconds; `CLAUDE_MULTIACC_AR_PANE_WAIT` (15 s)
491
+ allows for that.
492
+ - **An optional backstop,** off by default: with `CLAUDE_MULTIACC_AR_MEMORY_LIMIT_MB=<MB>`
493
+ set, the client's own memory at or over that many megabytes counts too
494
+ (`code=memory_limit src=process`), read without a child process: its phys_footprint
495
+ (Activity Monitor's Memory) through libproc on macOS, VmRSS + VmSwap on Linux. That is
496
+ the whole process: more than the heap the banner measures, so set the limit well above
497
+ 2.5 GiB.
253
498
 
254
499
  ## What gets a watcher
255
500
 
@@ -266,7 +511,8 @@ launch.
266
511
  `--effort <e>`, `--permission-mode <m>`, and at most one prompt argument that
267
512
  contains a space.
268
513
  - **codex:** `--dangerously-bypass-approvals-and-sandbox`, `--yolo`, `--no-daemon`,
269
- `-m` / `--model <m>`, `resume <uuid>|--last`, and at most one prompt that contains a
514
+ `-m` / `--model <m>`, `-c model_reasoning_effort=<level>` (the only `-c` accepted,
515
+ with a lowercase level), `resume <uuid>|--last`, and at most one prompt that contains a
270
516
  space. The conservative pair `--sandbox read-only --ask-for-approval on-request`
271
517
  is also supported; partial, conflicting or other sandbox/approval combinations are not.
272
518
  - **Anything else means no watcher.** That includes `-p` (for codex, `-p` is
@@ -293,13 +539,14 @@ tokens.
293
539
  | Event | Written by | Meaning |
294
540
  | --- | --- | --- |
295
541
  | `watch` | watcher | supervision started for a pid |
296
- | `detect` | watcher | a classified error is pending |
542
+ | `detect` | watcher | a classified error is pending (`src=sidechain agent=<id>` when a subagent's transcript recorded it); `class=memory code=critical_memory size=<size> src=pane` for the client's memory banner (`code=memory_limit src=process` for the opt-in backstop) |
297
543
  | `never` | watcher | an error auto-resume does not handle; ignored |
298
- | `hold` | watcher | no other account has headroom, so nothing is stopped |
299
- | `switch` | watcher | stopped the session and typed the relaunch (`from=`, `class=`, `sid=`, `depth=`) |
544
+ | `hold` | watcher | the verdict stands but nothing is stopped yet: no other account has headroom (`reason=no-room`), a budget is spent, or an agent's or a memory verdict waits for the main thread's own work (`reason=main-request`, `main-stream`, `main-tool`; a memory verdict also `main-compact`) |
545
+ | `switch` | watcher | stopped the session and typed the relaunch (`from=`, `class=`, `sid=`, `depth=`; `model=` / `effort=` when the relaunch pins them; `size=` for a memory restart) |
300
546
  | `crash` | watcher | claude ended unexpectedly; relaunching |
301
- | `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 |
547
+ | `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. `reason=memory-budget class=memory`: memory restarts are over for that process, and the watcher goes on with every other verdict. `reason=memory-<pane, pane-shell, background, pgrp, holder, cwd, sync, mode, write> class=memory`: that check refused a memory restart before anything was stopped; the verdict is dropped, the pane is not read for `MEMORY_BACKOFF` s, and the watcher goes on |
302
548
  | `relaunch` | the relaunched shim | consumed the token (`chain=`, `from=`, `class=`, `depth=`) |
549
+ | `trust-failed` | the relaunched codex shim | could not mark its cwd trusted in the picked account (`chain=`, `acct=`); the TUI may ask for folder access |
303
550
 
304
551
  Cross-provider `switch` records also include `to_provider` and the newly confirmed
305
552
  destination conversation's `to_sid`; `sid` continues to identify the source conversation.
@@ -349,12 +596,22 @@ CLAUDE_MULTIACC_AR_PROBE=1 CLAUDE_MULTIACC_AR_AVOID=acct-07:1790000000 claude
349
596
  descendants in its process group. Detached work and a pre-existing shared Codex daemon
350
597
  may survive; the continuation prompt requires checking before relying on or restarting
351
598
  that work. For Claude model-limit and transient errors, the watcher first waits up to
352
- 15 min for subagent transcripts to go quiet.
599
+ 15 min for subagent transcripts to go quiet. An account verdict a subagent recorded
600
+ moves the session after the usual 5 s even while the main thread is still running a
601
+ tool: the account would refuse that thread's next request, and the tool is stopped.
353
602
  - **Only the pane's own shell is typed into.** The relaunch goes only to the shell that
354
603
  launched the session. A session started from a nested shell or a script, or with
355
604
  `exec claude`, gets a watcher that gives up instead of relaunching.
356
605
  - **Typeahead is discarded.** Text sitting at the shell prompt when the relaunch is typed
357
606
  is cleared.
607
+ - **Critical memory is seen only on screen.** The banner is read from the pane, so it goes
608
+ unseen while the prompt box is not drawn (the transcript view, a dialog), when the pane is
609
+ too narrow to show its size, or in a client that words or places it differently. A
610
+ fullscreen client (`"tui": "fullscreen"`, `CLAUDE_CODE_NO_FLICKER=1`; the pool's accounts
611
+ run `"tui": "default"`) draws its notifications over the row above the prompt box. That
612
+ row is among the conversation's, where the banner cannot be told from a reply quoting it,
613
+ so it is not read. The session then stays as it is; `CLAUDE_MULTIACC_AR_MEMORY_LIMIT_MB`
614
+ is the backstop for that.
358
615
  - **Codex goals do not follow yet.** Codex keeps `/goal` state per account
359
616
  (`goals_1.sqlite`), so set the goal again after a codex rotation. Claude restores a goal
360
617
  from the transcript on resume.
package/docs/CODEX.md CHANGED
@@ -208,6 +208,7 @@ codex-accounts list
208
208
  codex-accounts status
209
209
  codex-accounts verify --quick # check local auth without inference
210
210
  codex-accounts verify # real codex exec call per account
211
+ codex-accounts model-catalog show # the model/effort catalog auto-resume matches with
211
212
  codex-accounts --help # all account commands and options
212
213
  claude-multiacc codex status # same status command via the npm wrapper
213
214
  ```
@@ -46,7 +46,13 @@ postinstall, so `npm i -g claude-multiacc@latest` fully updates the shim, CLI, a
46
46
  A daily job (launchd on macOS, cron on Linux, 04:07) runs `claude-accounts self-update`
47
47
  to pull the latest release on its own — `npm i -g claude-multiacc@latest` for npm installs,
48
48
  `git pull && ./install.sh` for git checkouts; set `CLAUDE_MULTIACC_AUTOUPDATE=0` before install to
49
- opt out. `update-notifier` also prints a one-line nudge when a newer version is on npm.
49
+ opt out. An npm install only ever moves UP: it installs the registry's version only when that
50
+ is newer than its own, and nothing when it cannot read the registry's version. A build
51
+ installed by hand ahead of a publish must carry a version at or above the registry's (the
52
+ registry's own, or the next patch, which `node scripts/auto-version.mjs` writes into
53
+ `package.json` in a checkout); it then stays until a newer release is published. A git
54
+ checkout pulls with `--ff-only`. `update-notifier` also prints a one-line nudge when a newer
55
+ version is on npm.
50
56
  (Note: newer npm versions gate install scripts; if postinstall is blocked, just run
51
57
  `claude-multiacc install` once.)
52
58
 
@@ -88,6 +88,10 @@ repointed via `CLAUDE_BIN=/usr/local/bin/claude` and restarted healthy.
88
88
 
89
89
  - Tokens and credentials live as 0600 files under `~/.claude-accounts` (and
90
90
  `/root/.claude-accounts` on the server). Server compromise = account access.
91
+ - `~/.claude-accounts/mcp-oauth.json` (0600) holds the MCP servers' OAuth logins every
92
+ account on this machine shares (README, *MCP OAuth logins*). It never leaves the machine:
93
+ sync and the limits fan-out push explicit file lists that do not name it, and
94
+ `mcp auth-status` / `ops.log` name servers, accounts and expiries, never a token.
91
95
  - `selection.log` records timestamps, accounts and cwd, and for auto-resume also
92
96
  session ids and error classes. It never records prompt or transcript text.
93
97
  - Rotating multiple subscriptions to spread usage may be flagged by anti-abuse systems;