claude-multiacc 2.0.56 → 2.0.57
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 +7 -2
- package/bin/claude +71 -8
- package/bin/codex +67 -14
- package/docs/AUTORESUME-HANDOFF.md +82 -0
- package/docs/AUTORESUME.md +16 -5
- package/lib/__pycache__/audit.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross_dispatch.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross_receipt.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_handoff.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_probe.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_receipt.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_client_error.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_token_identity.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_autoresume_mode.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_controls.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_local_scope.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_selection.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credits.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_limits_lock.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_usage_fetch.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__/report.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_credit_scope.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_credits.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_funding.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 +58 -14
- package/lib/autoresume_cross.py +143 -0
- package/lib/autoresume_cross_dispatch.py +114 -0
- package/lib/autoresume_cross_receipt.py +138 -0
- package/lib/autoresume_handoff.py +191 -0
- package/lib/autoresume_probe.py +36 -0
- package/package.json +1 -1
- package/tests/__pycache__/autoresume_cross_receipt_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_tmux_environment.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_tmux_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_unit_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_tmux_support.cpython-312.pyc +0 -0
- 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_autoresume_codex_records.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_claude_holders.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_guards.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_holders.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_policy.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_receipt.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tmux_races.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tokens.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cursor.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_fallback.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_argv.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_history.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_mode.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_probe.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_receipt.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_tmux_modes.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_claude_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_claude_token_identity.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits_report.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_fetch.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/__pycache__/test_selector.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_cached_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credit_scope.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credit_shim.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_spread.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_ceremony.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_integration.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_retry.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_sync.cpython-312.pyc +0 -0
- package/tests/autoresume_cross_receipt_support.py +58 -0
- package/tests/autoresume_cross_tmux_environment.py +59 -0
- package/tests/autoresume_cross_tmux_support.py +112 -0
- package/tests/autoresume_cross_unit_support.py +92 -0
- package/tests/fixtures/cross_provider_cli.py +121 -0
- package/tests/test_autoresume.py +3 -1
- package/tests/test_autoresume_cross_claude_holders.py +81 -0
- package/tests/test_autoresume_cross_guards.py +133 -0
- package/tests/test_autoresume_cross_holders.py +89 -0
- package/tests/test_autoresume_cross_policy.py +123 -0
- package/tests/test_autoresume_cross_receipt.py +141 -0
- package/tests/test_autoresume_cross_tmux.py +140 -0
- package/tests/test_autoresume_cross_tmux_races.py +45 -0
- package/tests/test_autoresume_cross_tokens.py +102 -0
- package/tests/test_autoresume_handoff_argv.py +127 -0
- package/tests/test_autoresume_handoff_history.py +115 -0
package/README.md
CHANGED
|
@@ -442,7 +442,9 @@ Interactive sessions are not retried this way. They get auto-resume, described n
|
|
|
442
442
|
|
|
443
443
|
An interactive `claude` or `codex` session in **tmux** can stop on a usage limit, a failed
|
|
444
444
|
login, an API error or (claude) a crash. It is then **resumed automatically, same session
|
|
445
|
-
id**, on another pooled account with headroom.
|
|
445
|
+
id**, on another pooled account with headroom. If a usage limit exhausts that provider's
|
|
446
|
+
usable accounts, it can continue in the other provider with instructions to read the
|
|
447
|
+
original conversation's history. There is no `/exit`, no `--resume` and no
|
|
446
448
|
"continue" to type. It covers the everyday launches:
|
|
447
449
|
|
|
448
450
|
- `claude --dangerously-skip-permissions`, `--resume <id>` and `--model …`;
|
|
@@ -458,7 +460,8 @@ Every other launch, including `-p` and `codex exec`, runs exactly as before.
|
|
|
458
460
|
it, so the client owns the task and rollout through account changes.
|
|
459
461
|
- **Probe.** Once an error settles, the watcher asks the shim, in probe mode, whether
|
|
460
462
|
another account has headroom, including the 90–99% fallback for quota errors. If none
|
|
461
|
-
does,
|
|
463
|
+
does, a quota recovery checks the other provider's pool. If neither can serve, it
|
|
464
|
+
**holds** and Claude's own "continuing automatically at …" carries on.
|
|
462
465
|
- **Relaunch.** Otherwise it stops the TUI with SIGTERM and waits for the pane's shell
|
|
463
466
|
prompt. It then types a one-line relaunch of the shim into that **shell**. It never
|
|
464
467
|
sends a keystroke to the TUI, whose limit menu can add funds or spend the one-shot
|
|
@@ -477,6 +480,8 @@ CLAUDE_MULTIACC_AUTORESUME=0 claude # one launch (CODEX_MULTIACC_AUTORESUM
|
|
|
477
480
|
```
|
|
478
481
|
|
|
479
482
|
See [auto-resume: error classes, gates, budgets, logs and limitations](docs/AUTORESUME.md).
|
|
483
|
+
See [continuing between Codex and Claude](docs/AUTORESUME-HANDOFF.md) for history,
|
|
484
|
+
permission handling and the cross-provider opt-out.
|
|
480
485
|
|
|
481
486
|
## MCP servers for every account
|
|
482
487
|
|
package/bin/claude
CHANGED
|
@@ -196,6 +196,10 @@ if [ -n "${CLAUDE_CONFIG_DIR:-}" ] || [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ] \
|
|
|
196
196
|
# An auto-resume probe asks who WOULD be picked and must never start a client; a
|
|
197
197
|
# passthrough has no pool pick to report (see ---- auto-resume ----).
|
|
198
198
|
[ "${CLAUDE_MULTIACC_AR_PROBE:-}" = "1" ] && { printf 'pick= tier=none\n'; exit 3; }
|
|
199
|
+
if [ -n "${CLAUDE_MULTIACC_AR:-}${CLAUDE_MULTIACC_AR_CROSS_CARRY:-}" ]; then
|
|
200
|
+
printf 'claude-multiacc: auto-resume cannot enter account selection.\n' >&2
|
|
201
|
+
exit 2
|
|
202
|
+
fi
|
|
199
203
|
mcp_nested_mirror "$@" || true
|
|
200
204
|
exec "$REAL" "$@"
|
|
201
205
|
fi
|
|
@@ -300,9 +304,9 @@ sel_log() {
|
|
|
300
304
|
# picked now?" — the real candidate loop, then `pick=<acct> tier=<t>` and exit, so the
|
|
301
305
|
# watcher only kills a session there is somewhere to move to.
|
|
302
306
|
# Everything fails OPEN: a missing python, a non-tmux terminal or an argv this code does
|
|
303
|
-
# not recognise all mean "exec exactly as before, unsupervised".
|
|
304
|
-
#
|
|
305
|
-
#
|
|
307
|
+
# not recognise all mean "exec exactly as before, unsupervised". A typed relaunch whose
|
|
308
|
+
# token is gone exits 2 instead of starting a fresh session. A cross-provider handoff
|
|
309
|
+
# also requires remaining quota, a startup receipt and successful watcher setup.
|
|
306
310
|
# Tokens and relaunch/state files live in $ACC_ROOT/tmp/autoresume; the watcher prunes.
|
|
307
311
|
AR_DIR="$ACC_ROOT/tmp/autoresume"
|
|
308
312
|
AR_PROBE=0
|
|
@@ -311,6 +315,8 @@ AR_DEPTH=0 # relaunches so far in this chain (the watcher caps it)
|
|
|
311
315
|
AR_AVOID="" # acct-NN:<until epoch>,... — accounts this chain just left
|
|
312
316
|
AR_HIST="" # class:epoch,... — the watcher's hourly budgets
|
|
313
317
|
AR_CHAIN="" # opaque chain id, minted and read by the watcher
|
|
318
|
+
AR_CROSS="" AR_HANDOFF_TOKEN=""
|
|
319
|
+
AR_AVOID_CLAUDE="" AR_AVOID_CODEX="" AR_POOL_CLAUDE="" AR_POOL_CODEX=""
|
|
314
320
|
AR_LOADED=0
|
|
315
321
|
AR_ARGV=()
|
|
316
322
|
AR_ORIG_ARGV=()
|
|
@@ -337,7 +343,7 @@ ar_field_ok() { # $1 value
|
|
|
337
343
|
# later, by ar_relaunch_apply, still ahead of any selection.
|
|
338
344
|
ar_relaunch_load() { # $1 token -> 0 with AR_ARGV and the AR_R_*/chain fields set
|
|
339
345
|
local tok="$1" rf af line k v n=0 a="" ok=0 ver="" prov="" sid="" cwd=""
|
|
340
|
-
local cls="" acct="" reset="" rtype="" marked="" depth=0 avoid="" hist="" chain=""
|
|
346
|
+
local cls="" acct="" reset="" rtype="" marked="" depth=0 avoid="" hist="" chain="" cross_bad=0
|
|
341
347
|
# Alphanumerics only: the token becomes part of a path, so no '/' and no '..'.
|
|
342
348
|
case "$tok" in *[!A-Za-z0-9]*) return 1 ;; esac
|
|
343
349
|
[ "${#tok}" -ge 8 ] && [ "${#tok}" -le 64 ] || return 1
|
|
@@ -362,6 +368,15 @@ ar_relaunch_load() { # $1 token -> 0 with AR_ARGV and the AR_R_*/chain fields se
|
|
|
362
368
|
cwd) case "$v" in /*) cwd="$v" ;; esac ;;
|
|
363
369
|
depth) num_ok "$v" && depth="$v" ;;
|
|
364
370
|
avoid) ar_field_ok "$v" && avoid="$v" ;;
|
|
371
|
+
avoid_claude) ar_field_ok "$v" && AR_AVOID_CLAUDE="$v" || cross_bad=1 ;;
|
|
372
|
+
avoid_codex) ar_field_ok "$v" && AR_AVOID_CODEX="$v" || cross_bad=1 ;;
|
|
373
|
+
pool_claude) [ -z "$v" ] || { ar_path_ok "$v" && AR_POOL_CLAUDE="$v"; } || cross_bad=1 ;;
|
|
374
|
+
pool_codex) [ -z "$v" ] || { ar_path_ok "$v" && AR_POOL_CODEX="$v"; } || cross_bad=1 ;;
|
|
375
|
+
cross|handoff_token)
|
|
376
|
+
[ -n "$v" ] || continue
|
|
377
|
+
case "$v" in *[!A-Za-z0-9]*) cross_bad=1; continue ;; esac
|
|
378
|
+
[ "${#v}" -ge 8 ] && [ "${#v}" -le 64 ] || { cross_bad=1; continue; }
|
|
379
|
+
if [ "$k" = cross ]; then AR_CROSS="$v"; else AR_HANDOFF_TOKEN="$v"; fi ;;
|
|
365
380
|
hist) ar_field_ok "$v" && hist="$v" ;;
|
|
366
381
|
chain) ar_field_ok "$v" && [ "${#v}" -le 64 ] && chain="$v" ;;
|
|
367
382
|
esac
|
|
@@ -380,6 +395,8 @@ ar_relaunch_load() { # $1 token -> 0 with AR_ARGV and the AR_R_*/chain fields se
|
|
|
380
395
|
fi
|
|
381
396
|
# Single use, whatever the outcome: a token is never honoured twice.
|
|
382
397
|
rm -f "$rf" "$af" 2>/dev/null
|
|
398
|
+
[ "$cross_bad" = 0 ] || ok=0
|
|
399
|
+
[ -z "$AR_HANDOFF_TOKEN" ] || { [ -z "$acct" ] && [ -z "$AR_CROSS" ]; } || ok=0
|
|
383
400
|
if [ "$ok" != 1 ]; then
|
|
384
401
|
AR_ARGV=()
|
|
385
402
|
AR_R_SID="$sid"
|
|
@@ -587,14 +604,20 @@ ar_start_watcher() { # $1 provider, $2 picked acct dir
|
|
|
587
604
|
printf '%s\n' "v=1" "provider=$prov" "pid=$$" "ppid=$PPID" "acct=${dir##*/}" \
|
|
588
605
|
"acct_dir=$dir" "cwd=$PWD" "launched=$launched" "tmux=$TMUX" "pane=$TMUX_PANE" \
|
|
589
606
|
"self=$SELF" "acc_root=$root" "depth=$AR_DEPTH" "avoid=$AR_AVOID" \
|
|
590
|
-
"hist=$AR_HIST" "chain=$AR_CHAIN"
|
|
607
|
+
"hist=$AR_HIST" "chain=$AR_CHAIN" "avoid_claude=$AR_AVOID_CLAUDE" \
|
|
608
|
+
"avoid_codex=$AR_AVOID_CODEX" "pool_claude=$AR_POOL_CLAUDE" "pool_codex=$AR_POOL_CODEX" \
|
|
609
|
+
"handoff_token=$AR_HANDOFF_TOKEN" 2>/dev/null > "$tmp" \
|
|
591
610
|
&& mv -f "$tmp" "$st" 2>/dev/null \
|
|
592
611
|
|| { rm -f "$tmp" 2>/dev/null; return 1; }
|
|
612
|
+
if [ -n "$AR_HANDOFF_TOKEN" ]; then
|
|
613
|
+
"$AR_PY" -I "$SELF_DIR/../lib/autoresume_cross_dispatch.py" startup \
|
|
614
|
+
--state "$st" --token "$AR_HANDOFF_TOKEN" </dev/null || return 1
|
|
615
|
+
fi
|
|
593
616
|
# Detached and never waited for; the watcher setsid()s itself at once. It inherits
|
|
594
617
|
# this environment on purpose (the probe strips the account-specific part).
|
|
595
618
|
( trap '' INT QUIT HUP TSTP
|
|
596
619
|
exec "$AR_PY" -I "$lib" watch --state "$st" </dev/null >/dev/null 2>&1 &
|
|
597
|
-
) >/dev/null 2>&1
|
|
620
|
+
) >/dev/null 2>&1 || return 1
|
|
598
621
|
return 0
|
|
599
622
|
}
|
|
600
623
|
|
|
@@ -642,7 +665,8 @@ fi
|
|
|
642
665
|
# `exec "$SELF"`. A relaunch has spent its token by then, so it hands its chain (depth,
|
|
643
666
|
# the accounts it left, its budgets) over in the environment — without it the re-entered
|
|
644
667
|
# selection could land right back on the account the session just left. Only such a
|
|
645
|
-
# re-entry (PREFLIGHT_DEPTH set) may use it, and never a probe or a fresh token.
|
|
668
|
+
# re-entry (PREFLIGHT_DEPTH set) may use it, and never a probe or a fresh token. The
|
|
669
|
+
# separate CROSS_CARRY keeps both pools and their avoid lists for later reverse hops.
|
|
646
670
|
if [ -n "${CLAUDE_MULTIACC_AR_CARRY+x}" ]; then
|
|
647
671
|
ar_c="$CLAUDE_MULTIACC_AR_CARRY"
|
|
648
672
|
unset CLAUDE_MULTIACC_AR_CARRY
|
|
@@ -657,6 +681,22 @@ if [ -n "${CLAUDE_MULTIACC_AR_CARRY+x}" ]; then
|
|
|
657
681
|
fi
|
|
658
682
|
unset ar_c ar_d ar_a ar_h
|
|
659
683
|
fi
|
|
684
|
+
if [ -n "${CLAUDE_MULTIACC_AR_CROSS_CARRY+x}" ]; then
|
|
685
|
+
if [ "$AR_PROBE" != 1 ] && [ "$AR_LOADED" != 1 ] && [ -n "${CLAUDE_MULTIACC_PREFLIGHT_DEPTH:-}" ]; then
|
|
686
|
+
IFS='|' read -r ar_t ar_c ar_x ar_pc ar_px ar_extra <<< "$CLAUDE_MULTIACC_AR_CROSS_CARRY"
|
|
687
|
+
if ar_field_ok "$ar_c" && ar_field_ok "$ar_x" && [ -z "$ar_extra" ] \
|
|
688
|
+
&& { [ -z "$ar_pc" ] || ar_path_ok "$ar_pc"; } && { [ -z "$ar_px" ] || ar_path_ok "$ar_px"; }; then
|
|
689
|
+
case "$ar_t" in *[!A-Za-z0-9]*) exit 2 ;; esac
|
|
690
|
+
[ -z "$ar_t" ] || { [ "${#ar_t}" -ge 8 ] && [ "${#ar_t}" -le 64 ]; } || exit 2
|
|
691
|
+
AR_HANDOFF_TOKEN="$ar_t"; AR_AVOID_CLAUDE="$ar_c"; AR_AVOID_CODEX="$ar_x"
|
|
692
|
+
AR_POOL_CLAUDE="$ar_pc"; AR_POOL_CODEX="$ar_px"
|
|
693
|
+
else
|
|
694
|
+
printf 'claude-multiacc: auto-resume preflight state is invalid.\n' >&2
|
|
695
|
+
exit 2
|
|
696
|
+
fi
|
|
697
|
+
fi
|
|
698
|
+
unset CLAUDE_MULTIACC_AR_CROSS_CARRY ar_t ar_c ar_x ar_pc ar_px ar_extra
|
|
699
|
+
fi
|
|
660
700
|
AR_ORIG_ARGV=(${1+"$@"})
|
|
661
701
|
|
|
662
702
|
# ---- model-scoped limits ------------------------------------------------------
|
|
@@ -1752,6 +1792,14 @@ token_preflight() { # $1 acct dir, $2 setup-token; rc 1 only for proven unusable
|
|
|
1752
1792
|
|
|
1753
1793
|
# A relaunch marks the account it left before anything reads the pool (auto-resume).
|
|
1754
1794
|
ar_relaunch_apply
|
|
1795
|
+
if [ -n "$AR_CROSS" ]; then
|
|
1796
|
+
[ -z "$AR_HANDOFF_TOKEN" ] && ar_python || exit 2
|
|
1797
|
+
exec "$AR_PY" -I "$SELF_DIR/../lib/autoresume_cross_dispatch.py" dispatch \
|
|
1798
|
+
--root "$ACC_ROOT" --token "$AR_CROSS"
|
|
1799
|
+
fi
|
|
1800
|
+
if [ -n "$AR_HANDOFF_TOKEN" ]; then
|
|
1801
|
+
[ -z "${CLAUDE_ACCOUNT:-}" ] && ar_argv_ok "$@" || exit 2
|
|
1802
|
+
fi
|
|
1755
1803
|
|
|
1756
1804
|
# Explicit pin wins over everything — markers, and even missing auth: the
|
|
1757
1805
|
# add/login ceremony pins to a dir that has no credentials yet, and the login
|
|
@@ -1854,6 +1902,10 @@ fi
|
|
|
1854
1902
|
# the pool is merely un-authenticated: this is the one case the user can actually fix.
|
|
1855
1903
|
if [ "${#valid[@]}" -eq 0 ]; then
|
|
1856
1904
|
[ "$AR_PROBE" = 1 ] && ar_probe_exit none
|
|
1905
|
+
if [ -n "$AR_HANDOFF_TOKEN" ]; then
|
|
1906
|
+
printf 'claude-multiacc: auto-resume handoff has no usable account.\n' >&2
|
|
1907
|
+
exit 2
|
|
1908
|
+
fi
|
|
1857
1909
|
if [ "${#expired[@]}" -gt 0 ]; then
|
|
1858
1910
|
sel_log "all-expired: falling back to the default login (see: claude-accounts expired)"
|
|
1859
1911
|
# Terminal only: a service-spawned `claude -p` must keep its stderr byte-clean, and
|
|
@@ -2099,6 +2151,10 @@ pick="$PICK_DIR"
|
|
|
2099
2151
|
# An auto-resume probe ends here: before the token preflight (a real inference), the
|
|
2100
2152
|
# model pin, remember_pick, the limits kick and the pick line — it chose nothing.
|
|
2101
2153
|
[ "$AR_PROBE" = 1 ] && ar_probe_exit
|
|
2154
|
+
if [ -n "$AR_HANDOFF_TOKEN" ] && [ "${#eligible[@]}" -eq 0 ] && [ "${#soft[@]}" -eq 0 ]; then
|
|
2155
|
+
printf 'claude-multiacc: auto-resume handoff has no remaining quota.\n' >&2
|
|
2156
|
+
exit 2
|
|
2157
|
+
fi
|
|
2102
2158
|
|
|
2103
2159
|
# The preflight can prove the selected token dead without exposing its 401 to the
|
|
2104
2160
|
# caller. Re-enter selection so the new .expired marker is applied and another
|
|
@@ -2117,6 +2173,10 @@ if [ -n "$tok" ] && ! token_preflight "$pick" "$tok"; then
|
|
|
2117
2173
|
# An auto-resume chain survives the re-entry (see ---- auto-resume ----). '|' is
|
|
2118
2174
|
# outside every chain field's charset, so it separates them unambiguously.
|
|
2119
2175
|
[ -n "$AR_CHAIN" ] && export CLAUDE_MULTIACC_AR_CARRY="$AR_DEPTH|$AR_AVOID|$AR_HIST|$AR_CHAIN"
|
|
2176
|
+
if [ -n "$AR_HANDOFF_TOKEN$AR_AVOID_CLAUDE$AR_AVOID_CODEX$AR_POOL_CLAUDE$AR_POOL_CODEX" ]; then
|
|
2177
|
+
ar_cross_carry="$AR_HANDOFF_TOKEN|$AR_AVOID_CLAUDE|$AR_AVOID_CODEX"
|
|
2178
|
+
export CLAUDE_MULTIACC_AR_CROSS_CARRY="$ar_cross_carry|$AR_POOL_CLAUDE|$AR_POOL_CODEX"
|
|
2179
|
+
fi
|
|
2120
2180
|
exec "$SELF" "$@"
|
|
2121
2181
|
fi
|
|
2122
2182
|
printf 'claude-multiacc: portable token preflight retry limit reached\n' >&2
|
|
@@ -2262,7 +2322,10 @@ if [ "$wants_retry" = "0" ]; then
|
|
|
2262
2322
|
[ -n "$tok" ] && export CLAUDE_CODE_OAUTH_TOKEN="$tok"
|
|
2263
2323
|
sel_capture_session "$pick"
|
|
2264
2324
|
ar_trust "$pick"
|
|
2265
|
-
ar_spawn_watcher claude "$pick"
|
|
2325
|
+
if ! ar_spawn_watcher claude "$pick" && [ -n "$AR_HANDOFF_TOKEN" ]; then
|
|
2326
|
+
printf 'claude-multiacc: auto-resume handoff could not start its watcher.\n' >&2
|
|
2327
|
+
exit 2
|
|
2328
|
+
fi
|
|
2266
2329
|
exec "$REAL" "$@"
|
|
2267
2330
|
fi
|
|
2268
2331
|
|
package/bin/codex
CHANGED
|
@@ -181,6 +181,10 @@ if [ -n "${CODEX_HOME:-}" ] \
|
|
|
181
181
|
# An auto-resume probe (see "auto-resume" below) asks which account selection would
|
|
182
182
|
# pick; with no selection to run, the answer is none — it must never start a session.
|
|
183
183
|
if [ "${CODEX_MULTIACC_AR_PROBE:-0}" = "1" ]; then printf 'pick= tier=none\n'; exit 3; fi
|
|
184
|
+
if [ -n "${CODEX_MULTIACC_AR:-}" ]; then
|
|
185
|
+
printf 'codex-multiacc: auto-resume cannot enter account selection.\n' >&2
|
|
186
|
+
exit 2
|
|
187
|
+
fi
|
|
184
188
|
mcp_nested_mirror "$@" || true
|
|
185
189
|
exec "$REAL" "$@"
|
|
186
190
|
fi
|
|
@@ -749,14 +753,16 @@ auth_dead() { expired_marked "$1"; }
|
|
|
749
753
|
# picks the next account. bin/claude carries the same hooks; docs/AUTORESUME.md is the
|
|
750
754
|
# reference. Fail open everywhere: no network; the local CLI mode probe is bounded to
|
|
751
755
|
# two seconds, and every path that cannot finish falls through
|
|
752
|
-
# to exactly today's exec.
|
|
753
|
-
#
|
|
754
|
-
#
|
|
756
|
+
# to exactly today's exec. A typed relaunch whose token is gone exits 2 instead of
|
|
757
|
+
# starting a fresh session. A cross-provider handoff also requires remaining quota,
|
|
758
|
+
# a startup receipt and successful watcher setup.
|
|
755
759
|
AR_DIR="$ACC_ROOT/tmp/autoresume"
|
|
756
760
|
AR_DEPTH=0 # how many relaunches this session's chain has already taken
|
|
757
761
|
AR_AVOID="" # acct-NN:until,... — accounts the chain must not land on again
|
|
758
762
|
AR_HIST="" # class:epoch,... — the chain's verdicts, for the watcher's budgets
|
|
759
763
|
AR_CHAIN="" # the chain's id, as the watcher named it
|
|
764
|
+
AR_CROSS="" AR_HANDOFF_TOKEN=""
|
|
765
|
+
AR_AVOID_CLAUDE="" AR_AVOID_CODEX="" AR_POOL_CLAUDE="" AR_POOL_CODEX=""
|
|
760
766
|
AR_ARGV=()
|
|
761
767
|
AR_ORIG_ARGV=()
|
|
762
768
|
AR_NO_DAEMON=0
|
|
@@ -898,7 +904,7 @@ ar_token_dead() { # $1 session id (the token's, else the file's) or empty
|
|
|
898
904
|
ar_relaunch() { # $1 token
|
|
899
905
|
local tok="$1" base f line k v n=0 a age
|
|
900
906
|
local ver="" prov="" cls="" acct="" sid="" cwd="" depth="" avoid="" hist="" chain=""
|
|
901
|
-
local funding="" funding_pid=""
|
|
907
|
+
local funding="" funding_pid="" cross_bad=0
|
|
902
908
|
ar_word_ok "$tok" 8 || return 1
|
|
903
909
|
base="$AR_DIR/r-$tok"
|
|
904
910
|
f="$base.relaunch"
|
|
@@ -920,6 +926,14 @@ ar_relaunch() { # $1 token
|
|
|
920
926
|
cwd) case "$v" in /*) cwd="$v" ;; esac ;;
|
|
921
927
|
depth) num_ok "$v" && depth="$v" ;;
|
|
922
928
|
avoid) avoid="$v" ;;
|
|
929
|
+
avoid_claude|avoid_codex)
|
|
930
|
+
case "$v" in *[!A-Za-z0-9:,._-]*) cross_bad=1; continue ;; esac
|
|
931
|
+
[ "${#v}" -le 2048 ] || { cross_bad=1; continue; }
|
|
932
|
+
if [ "$k" = avoid_claude ]; then AR_AVOID_CLAUDE="$v"; else AR_AVOID_CODEX="$v"; fi ;;
|
|
933
|
+
pool_claude) [ -z "$v" ] || { ar_path_ok "$v" && AR_POOL_CLAUDE="$v"; } || cross_bad=1 ;;
|
|
934
|
+
pool_codex) [ -z "$v" ] || { ar_path_ok "$v" && AR_POOL_CODEX="$v"; } || cross_bad=1 ;;
|
|
935
|
+
cross) [ -z "$v" ] || { ar_word_ok "$v" 8 && AR_CROSS="$v"; } || cross_bad=1 ;;
|
|
936
|
+
handoff_token) [ -z "$v" ] || { ar_word_ok "$v" 8 && AR_HANDOFF_TOKEN="$v"; } || cross_bad=1 ;;
|
|
923
937
|
hist) hist="$v" ;;
|
|
924
938
|
chain) ar_word_ok "$v" 1 && chain="$v" ;;
|
|
925
939
|
funding_tier) [ "$v" = credits ] && funding=credits ;;
|
|
@@ -927,8 +941,10 @@ ar_relaunch() { # $1 token
|
|
|
927
941
|
rollout_cursor) case "$v" in *[!0-9:]*|'') ;; *) AR_ROLLOUT_CURSOR="$v" ;; esac ;;
|
|
928
942
|
esac
|
|
929
943
|
done < "$f"
|
|
944
|
+
[ -z "$AR_HANDOFF_TOKEN" ] || { [ -z "$acct" ] && [ -z "$AR_CROSS" ]; } || cross_bad=1
|
|
930
945
|
age=$((now - $(file_mtime "$f")))
|
|
931
|
-
if [ "$
|
|
946
|
+
if [ "$cross_bad" = 1 ] || [ "$ver" != "1" ] || [ "$prov" != "codex" ] \
|
|
947
|
+
|| [ "$age" -gt 600 ] || [ ! -f "$base.argv" ]; then
|
|
932
948
|
rm -f "$f" "$base.argv" 2>/dev/null
|
|
933
949
|
AR_R_SID="$sid"
|
|
934
950
|
return 1
|
|
@@ -979,16 +995,24 @@ ar_relaunch() { # $1 token
|
|
|
979
995
|
# A positional must contain a space to count as a prompt: a single word may be a
|
|
980
996
|
# subcommand.
|
|
981
997
|
ar_argv_ok() { # the argv codex is about to get
|
|
982
|
-
local a resume=0 sid=0 prompt=0 want=0
|
|
998
|
+
local a resume=0 sid=0 prompt=0 want="" sandbox=0 approval=0 bypass=0
|
|
983
999
|
for a in "$@"; do
|
|
984
|
-
if [ "$want"
|
|
985
|
-
case "$a" in
|
|
986
|
-
|
|
1000
|
+
if [ -n "$want" ]; then
|
|
1001
|
+
case "$want:$a" in
|
|
1002
|
+
sandbox:read-only) sandbox=1 ;;
|
|
1003
|
+
approval:on-request) approval=1 ;;
|
|
1004
|
+
model:*) case "$a" in ''|-*) return 1 ;; esac ;;
|
|
1005
|
+
*) return 1 ;;
|
|
1006
|
+
esac
|
|
1007
|
+
want=""
|
|
987
1008
|
continue
|
|
988
1009
|
fi
|
|
989
1010
|
case "$a" in
|
|
990
|
-
--dangerously-bypass-approvals-and-sandbox|--yolo
|
|
991
|
-
-
|
|
1011
|
+
--dangerously-bypass-approvals-and-sandbox|--yolo) bypass=1 ;;
|
|
1012
|
+
--no-daemon) ;;
|
|
1013
|
+
--sandbox) [ "$sandbox" = 0 ] || return 1; want=sandbox ;;
|
|
1014
|
+
--ask-for-approval) [ "$approval" = 0 ] || return 1; want=approval ;;
|
|
1015
|
+
-m|--model) want=model ;;
|
|
992
1016
|
--model=?*) ;;
|
|
993
1017
|
--last)
|
|
994
1018
|
[ "$resume" = 1 ] && [ "$sid" = 0 ] || return 1
|
|
@@ -1007,7 +1031,8 @@ ar_argv_ok() { # the argv codex is about to get
|
|
|
1007
1031
|
fi ;;
|
|
1008
1032
|
esac
|
|
1009
1033
|
done
|
|
1010
|
-
[ "$want" =
|
|
1034
|
+
[ -z "$want" ] && [ "$sandbox" = "$approval" ] || return 1
|
|
1035
|
+
[ "$sandbox" = 0 ] || [ "$bypass" = 0 ] || return 1
|
|
1011
1036
|
# A bare `resume` opens the session picker: there is no session to follow yet.
|
|
1012
1037
|
[ "$resume" = 0 ] || [ "$sid" = 1 ]
|
|
1013
1038
|
}
|
|
@@ -1102,16 +1127,23 @@ ar_spawn_watcher() { # $1 provider, $2 picked acct dir, then the argv codex is a
|
|
|
1102
1127
|
printf 'acct=%s\nacct_dir=%s\ncwd=%s\nlaunched=%s\n' "${pick##*/}" "$pick" "$PWD" "$(date +%s)"
|
|
1103
1128
|
printf 'tmux=%s\npane=%s\nself=%s\nacc_root=%s\n' "$TMUX" "$TMUX_PANE" "$SELF" "$root"
|
|
1104
1129
|
printf 'depth=%s\navoid=%s\nhist=%s\nchain=%s\n' "$AR_DEPTH" "$AR_AVOID" "$AR_HIST" "$AR_CHAIN"
|
|
1130
|
+
printf 'avoid_claude=%s\navoid_codex=%s\n' "$AR_AVOID_CLAUDE" "$AR_AVOID_CODEX"
|
|
1131
|
+
printf 'pool_claude=%s\npool_codex=%s\n' "$AR_POOL_CLAUDE" "$AR_POOL_CODEX"
|
|
1132
|
+
printf 'handoff_token=%s\n' "$AR_HANDOFF_TOKEN"
|
|
1105
1133
|
printf 'funding_tier=%s\n' "$AR_TIER"
|
|
1106
1134
|
printf 'rollout_cursor=%s\n' "$AR_ROLLOUT_CURSOR"
|
|
1107
1135
|
} 2>/dev/null > "$st.tmp" && mv -f "$st.tmp" "$st" 2>/dev/null || {
|
|
1108
1136
|
rm -f "$st.tmp" "$AR_DIR/$$.argv" 2>/dev/null
|
|
1109
1137
|
return 1
|
|
1110
1138
|
}
|
|
1139
|
+
if [ -n "$AR_HANDOFF_TOKEN" ]; then
|
|
1140
|
+
"$AR_PY" -I "$SELF_DIR/../lib/autoresume_cross_dispatch.py" startup \
|
|
1141
|
+
--state "$st" --token "$AR_HANDOFF_TOKEN" </dev/null || return 1
|
|
1142
|
+
fi
|
|
1111
1143
|
# Detached and never waited on: the subshell returns as soon as the watcher is forked,
|
|
1112
1144
|
# and the watcher ignores the terminal's signals until its own setsid().
|
|
1113
1145
|
( trap '' INT QUIT HUP TSTP
|
|
1114
|
-
exec "$AR_PY" -I "$lib" watch --state "$st" </dev/null >/dev/null 2>&1 & ) >/dev/null 2>&1
|
|
1146
|
+
exec "$AR_PY" -I "$lib" watch --state "$st" </dev/null >/dev/null 2>&1 & ) >/dev/null 2>&1 || return 1
|
|
1115
1147
|
[ "$mode" != --no-daemon ] || AR_NO_DAEMON=1
|
|
1116
1148
|
return 0
|
|
1117
1149
|
}
|
|
@@ -1141,6 +1173,14 @@ if [ -n "${CODEX_MULTIACC_AR+x}" ]; then
|
|
|
1141
1173
|
fi
|
|
1142
1174
|
[ -n "${CODEX_MULTIACC_AR_AVOID:-}" ] && ar_avoid_add "$CODEX_MULTIACC_AR_AVOID"
|
|
1143
1175
|
AR_ORIG_ARGV=("$@")
|
|
1176
|
+
if [ -n "$AR_CROSS" ]; then
|
|
1177
|
+
[ -z "$AR_HANDOFF_TOKEN" ] && ar_find_python || exit 2
|
|
1178
|
+
exec "$AR_PY" -I "$SELF_DIR/../lib/autoresume_cross_dispatch.py" dispatch \
|
|
1179
|
+
--root "$ACC_ROOT" --token "$AR_CROSS"
|
|
1180
|
+
fi
|
|
1181
|
+
if [ -n "$AR_HANDOFF_TOKEN" ]; then
|
|
1182
|
+
[ -z "${CODEX_ACCOUNT:-}" ] && ar_argv_ok "$@" || exit 2
|
|
1183
|
+
fi
|
|
1144
1184
|
|
|
1145
1185
|
# Explicit pin wins over everything — markers, and even missing auth: the
|
|
1146
1186
|
# add/login ceremony pins to a dir that has no credentials yet, and the login
|
|
@@ -1217,6 +1257,10 @@ fi
|
|
|
1217
1257
|
if [ "${#valid[@]}" -eq 0 ]; then
|
|
1218
1258
|
# A probe has nothing to offer and must never exec (nor log a fallback it never took).
|
|
1219
1259
|
if [ "$AR_PROBE" = "1" ]; then printf 'pick= tier=none\n'; exit 3; fi
|
|
1260
|
+
if [ -n "$AR_HANDOFF_TOKEN" ]; then
|
|
1261
|
+
printf 'codex-multiacc: auto-resume handoff has no usable account.\n' >&2
|
|
1262
|
+
exit 2
|
|
1263
|
+
fi
|
|
1220
1264
|
if [ "${#expired[@]}" -gt 0 ]; then
|
|
1221
1265
|
sel_log "all-expired: falling back to the default login (see: codex-accounts expired)"
|
|
1222
1266
|
# Terminal only: a service-spawned `codex exec` must keep its stderr byte-clean,
|
|
@@ -1430,6 +1474,12 @@ fi
|
|
|
1430
1474
|
# Remember the pick so the NEXT run does not hand back the same account. An explicit
|
|
1431
1475
|
# CODEX_ACCOUNT pin deliberately does not: a pin is a caller overriding selection,
|
|
1432
1476
|
# not a turn in the rotation.
|
|
1477
|
+
if [ -n "$AR_HANDOFF_TOKEN" ]; then
|
|
1478
|
+
case "$AR_TIER" in
|
|
1479
|
+
eligible|soft|credits) ;;
|
|
1480
|
+
*) printf 'codex-multiacc: auto-resume handoff has no remaining quota.\n' >&2; exit 2 ;;
|
|
1481
|
+
esac
|
|
1482
|
+
fi
|
|
1433
1483
|
remember_pick "$pick"
|
|
1434
1484
|
if [ "$AR_TIER" = "credits" ]; then
|
|
1435
1485
|
"$MCP_PYBIN" "$SELF_DIR/../lib/codex_credit_selection.py" record "$pick" "$$" 2>/dev/null || true
|
|
@@ -1507,7 +1557,10 @@ if [ "$wants_retry" = "0" ]; then
|
|
|
1507
1557
|
# Auto-resume: a gated, detached watcher beside this exec — the exec itself (same pid,
|
|
1508
1558
|
# same environment minus the auto-resume variables). Supported clients own their
|
|
1509
1559
|
# task through --no-daemon, including the continuation passed on resume.
|
|
1510
|
-
ar_spawn_watcher codex "$pick" "$@"
|
|
1560
|
+
if ! ar_spawn_watcher codex "$pick" "$@" && [ -n "$AR_HANDOFF_TOKEN" ]; then
|
|
1561
|
+
printf 'codex-multiacc: auto-resume handoff could not start its watcher.\n' >&2
|
|
1562
|
+
exit 2
|
|
1563
|
+
fi
|
|
1511
1564
|
ar_scrub_env
|
|
1512
1565
|
[ "$AR_NO_DAEMON" != 1 ] || set -- --no-daemon "$@"
|
|
1513
1566
|
exec "$REAL" "$@"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Continuing between Codex and Claude
|
|
2
|
+
|
|
3
|
+
When a supervised tmux session hits a usage limit, auto-resume first looks for another
|
|
4
|
+
usable account from the same provider. If that pool is exhausted, it checks the other
|
|
5
|
+
provider: Codex can continue in Claude, and Claude can continue in Codex.
|
|
6
|
+
|
|
7
|
+
The destination uses its normal account-selection policy. Eligible accounts, soft
|
|
8
|
+
fallback with remaining quota, and configured credits keep their existing meaning.
|
|
9
|
+
An exhausted destination account is not usable. If neither provider can serve, the
|
|
10
|
+
source client stays running and the watcher checks again later.
|
|
11
|
+
|
|
12
|
+
This fallback applies to quota errors. Login failures, model-specific limits, transient
|
|
13
|
+
errors and crashes retain their existing same-provider recovery behavior.
|
|
14
|
+
|
|
15
|
+
## Conversation continuity
|
|
16
|
+
|
|
17
|
+
A provider change starts a **new destination conversation**. Its first message names:
|
|
18
|
+
|
|
19
|
+
- the source provider and exact conversation ID;
|
|
20
|
+
- the validated absolute path to that conversation's local history;
|
|
21
|
+
- the original working directory and instructions to read the history before continuing.
|
|
22
|
+
|
|
23
|
+
The destination agent must recover the user's latest objective, completed work and
|
|
24
|
+
remaining work from that history. It also checks whether background processes or
|
|
25
|
+
subagents remain active before restarting or duplicating them. The source history stays
|
|
26
|
+
available; a Codex ID is never passed as a Claude native `--resume` ID, or vice versa.
|
|
27
|
+
|
|
28
|
+
The watcher uses the history already bound to the source client. It does not guess from
|
|
29
|
+
the newest transcript or directory name. Invalid, ambiguous or unavailable source
|
|
30
|
+
history prevents the handoff before the source client is stopped.
|
|
31
|
+
|
|
32
|
+
If another live client holds the same source conversation, the watcher holds to avoid
|
|
33
|
+
creating duplicate destination conversations. Stale process records do not count.
|
|
34
|
+
|
|
35
|
+
Success requires the destination to persist the exact handoff message as user input.
|
|
36
|
+
Starting the shell command or consuming a relaunch token is insufficient. The destination
|
|
37
|
+
then gets its own watcher, with the existing rotation chain and budgets preserved.
|
|
38
|
+
Account avoidance is separate for each provider, so Claude's `acct-01` and Codex's
|
|
39
|
+
`acct-01` cannot be confused.
|
|
40
|
+
|
|
41
|
+
## Permissions and arguments
|
|
42
|
+
|
|
43
|
+
The working directory is preserved. Source model names, effort settings, session
|
|
44
|
+
selectors and the original launch prompt are not replayed as destination CLI arguments.
|
|
45
|
+
The destination uses its own configured model.
|
|
46
|
+
|
|
47
|
+
An explicit permission bypass maps to the equivalent destination bypass. Merely allowing
|
|
48
|
+
a bypass flag is not treated as enabling it. Ordinary launches use explicit conservative
|
|
49
|
+
destination permissions so a pool default cannot silently grant broader access.
|
|
50
|
+
Codex receives `--sandbox read-only --ask-for-approval on-request`; Claude receives
|
|
51
|
+
`--permission-mode default`. Existing permission prompts may therefore still need you.
|
|
52
|
+
Restrictive modes without a safe equivalent keep the source session running instead of
|
|
53
|
+
relaxing its permissions.
|
|
54
|
+
|
|
55
|
+
## Controls
|
|
56
|
+
|
|
57
|
+
Cross-provider quota fallback is enabled by default. Disable it for launches from an
|
|
58
|
+
environment while retaining same-provider auto-resume:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
CODEX_MULTIACC_CROSS_PROVIDER=0 codex
|
|
62
|
+
CLAUDE_MULTIACC_CROSS_PROVIDER=0 claude
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Both providers' normal disable settings and auto-resume kill switches apply. For example,
|
|
66
|
+
`~/.claude-accounts/autoresume.off` prevents a Codex quota recovery from starting Claude.
|
|
67
|
+
Custom pools use their configured roots. The destination CLI must be installed and its
|
|
68
|
+
pool must have an account that can serve.
|
|
69
|
+
|
|
70
|
+
The process safeguards are the same as [ordinary auto-resume](AUTORESUME.md): validate
|
|
71
|
+
the client and pane, prepare the relaunch, stop only that client's job, then type into
|
|
72
|
+
the launching shell. No recovery keys are sent to a running TUI.
|
|
73
|
+
|
|
74
|
+
## Limits
|
|
75
|
+
|
|
76
|
+
Conversation history provides context; provider-specific in-memory state, running
|
|
77
|
+
subagents and native goal databases are not migrated. The continuation instructions
|
|
78
|
+
require the destination to inspect existing work before proceeding.
|
|
79
|
+
|
|
80
|
+
If capacity disappears between the probe and launch, the destination refuses to start
|
|
81
|
+
on an exhausted account. Missing confirmation of the handoff is reported as a failure,
|
|
82
|
+
not as a successful continuation.
|
package/docs/AUTORESUME.md
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
An interactive `claude` or `codex` session that stops on a usage limit, a failed login, an
|
|
4
4
|
API error or (claude only) a crash is restarted automatically on another pooled account
|
|
5
|
-
with headroom.
|
|
6
|
-
|
|
5
|
+
with headroom. Within the same provider it keeps the **same session id, directory and
|
|
6
|
+
flags**, and you press no keys. When a quota recovery has no usable account left in that
|
|
7
|
+
provider, it can continue in the other provider using the source conversation's history.
|
|
8
|
+
This covers sessions launched from a shell inside **tmux**. Every other launch runs
|
|
7
9
|
exactly as it did before.
|
|
8
10
|
|
|
9
11
|
It is on by default. The kill switches:
|
|
@@ -98,6 +100,11 @@ shell: runs the shim again → token → marker on the old account → normal
|
|
|
98
100
|
serve; an exhausted account or one with an active client rejection cannot.
|
|
99
101
|
- **Login, refusal and model-limit errors** still require a different account below
|
|
100
102
|
the exclusion threshold.
|
|
103
|
+
- **Quota recovery can cross providers.** Only after the source pool has no usable
|
|
104
|
+
account, the watcher checks the other provider's pool. It starts a fresh target
|
|
105
|
+
conversation with the exact source provider, session ID and validated history path.
|
|
106
|
+
Same-provider flags and session IDs are not blindly passed to the other CLI.
|
|
107
|
+
See [cross-provider continuation](AUTORESUME-HANDOFF.md).
|
|
101
108
|
- **Otherwise the watcher holds.** Nothing is stopped, and Claude's own "continuing
|
|
102
109
|
automatically" keeps running. The probe repeats every 60 s for as long as the error
|
|
103
110
|
stands.
|
|
@@ -258,8 +265,10 @@ launch.
|
|
|
258
265
|
`-c` / `--continue`, `-r` / `--resume <uuid>` (or `--resume=<uuid>`), `--model <m>`,
|
|
259
266
|
`--effort <e>`, `--permission-mode <m>`, and at most one prompt argument that
|
|
260
267
|
contains a space.
|
|
261
|
-
- **codex:** `--dangerously-bypass-approvals-and-sandbox`, `--yolo`,
|
|
262
|
-
`resume <uuid>|--last`, and at most one prompt that contains a
|
|
268
|
+
- **codex:** `--dangerously-bypass-approvals-and-sandbox`, `--yolo`, `--no-daemon`,
|
|
269
|
+
`-m` / `--model <m>`, `resume <uuid>|--last`, and at most one prompt that contains a
|
|
270
|
+
space. The conservative pair `--sandbox read-only --ask-for-approval on-request`
|
|
271
|
+
is also supported; partial, conflicting or other sandbox/approval combinations are not.
|
|
263
272
|
- **Anything else means no watcher.** That includes `-p` (for codex, `-p` is
|
|
264
273
|
`--profile`), a subcommand, `--fork-session` and any single-word argument, which
|
|
265
274
|
could be a subcommand.
|
|
@@ -292,6 +301,9 @@ tokens.
|
|
|
292
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 |
|
|
293
302
|
| `relaunch` | the relaunched shim | consumed the token (`chain=`, `from=`, `class=`, `depth=`) |
|
|
294
303
|
|
|
304
|
+
Cross-provider `switch` records also include `to_provider` and the newly confirmed
|
|
305
|
+
destination conversation's `to_sid`; `sid` continues to identify the source conversation.
|
|
306
|
+
|
|
295
307
|
Around these lines:
|
|
296
308
|
|
|
297
309
|
- The new pick logs its usual selection line.
|
|
@@ -347,7 +359,6 @@ CLAUDE_MULTIACC_AR_PROBE=1 CLAUDE_MULTIACC_AR_AVOID=acct-07:1790000000 claude
|
|
|
347
359
|
(`goals_1.sqlite`), so set the goal again after a codex rotation. Claude restores a goal
|
|
348
360
|
from the transcript on resume.
|
|
349
361
|
- **Out of scope for this version:**
|
|
350
|
-
- cross-provider continuation;
|
|
351
362
|
- proactive switching before a limit is hit;
|
|
352
363
|
- an agent that simply ends its turn early, which is not an error; use `/goal` for
|
|
353
364
|
that.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|