claude-multiacc 2.0.33 → 2.0.35
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/bin/claude +18 -0
- package/bin/claude-accounts +49 -0
- package/bin/codex-accounts +39 -0
- package/lib/__pycache__/audit.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__/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/package.json +1 -1
- package/tests/__pycache__/packaged_command_support.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/run-tests.sh +207 -25
package/bin/claude
CHANGED
|
@@ -301,6 +301,24 @@ client_marker_recovered() { # $1 account dir, $2 marker
|
|
|
301
301
|
*) bucket="" ;;
|
|
302
302
|
esac
|
|
303
303
|
case "$(sel_lc "$bucket")" in *seven_day*|*7d*|*weekly*) return 1 ;; esac
|
|
304
|
+
# ...and a SESSION rejection is only disproved by a reading that actually measured
|
|
305
|
+
# the session window. `peak` below is max_percent, which is the WEEKLY buckets' peak
|
|
306
|
+
# whenever the 5h bucket says nothing — clearing a five_hour marker on it borrows
|
|
307
|
+
# across signals exactly as the writer refuses to. 2026-09-22, acct-14: telemetry
|
|
308
|
+
# fetched WHILE the client was being rejected still read the session bucket
|
|
309
|
+
# `percent: 0, resets_at: null`, so the writer flags that 0 as inferred; the marker
|
|
310
|
+
# was cleared on it, `--resume` landed on the account, and the run died in a minute.
|
|
311
|
+
# A silent session bucket proves nothing about a 429 the client actually received.
|
|
312
|
+
case "$(sel_lc "$bucket")" in
|
|
313
|
+
*five_hour*|*5h*|*session*)
|
|
314
|
+
# A literal test, not limits_field: that parser is integer-only and its
|
|
315
|
+
# `[^0-9]*` would happily skip past `true` and return a digit from the NEXT
|
|
316
|
+
# field, which would read as "inferred" on every document.
|
|
317
|
+
LC_ALL=C grep -q '"session_inferred"[[:space:]]*:[[:space:]]*true' \
|
|
318
|
+
"$1/limits.json" 2>/dev/null && return 1
|
|
319
|
+
cutoff_field "$1" session_percent >/dev/null 2>&1 || return 1
|
|
320
|
+
;;
|
|
321
|
+
esac
|
|
304
322
|
fetched="$(telem_fetched_at "$1" 2>/dev/null || echo 0)"
|
|
305
323
|
# Defaulting peak to 100 when the reading is missing/stale is the second half of the
|
|
306
324
|
# same rule: an UNINFORMATIVE reading must never clear a marker. It is what keeps a
|
package/bin/claude-accounts
CHANGED
|
@@ -1651,6 +1651,15 @@ def marker_bucket(txt):
|
|
|
1651
1651
|
return part[7:]
|
|
1652
1652
|
return ''
|
|
1653
1653
|
|
|
1654
|
+
def session_marker(txt):
|
|
1655
|
+
# The self-healing 5h/session window, named the way each client writes it:
|
|
1656
|
+
# claude's own type is `five_hour`, codex labels a short window `client:5h`, and a
|
|
1657
|
+
# bare `session` covers the writer's own marker. Anything else (a weekly name, an
|
|
1658
|
+
# unparseable line) is NOT a session marker and keeps its existing handling.
|
|
1659
|
+
b = marker_bucket(txt).lower()
|
|
1660
|
+
return 'five_hour' in b or '5h' in b or 'session' in b
|
|
1661
|
+
|
|
1662
|
+
|
|
1654
1663
|
def weekly_marker(txt):
|
|
1655
1664
|
# Matched the way both shims match it (bin/claude ~303, same token list): the
|
|
1656
1665
|
# claude client writes client:seven_day / client:seven_day_opus, the codex client
|
|
@@ -2305,6 +2314,29 @@ for acct in manifest.get('accounts', []):
|
|
|
2305
2314
|
maxp = max([b['percent'] for b in live] or [0])
|
|
2306
2315
|
weeklyp = max(weekly) if weekly else None
|
|
2307
2316
|
sessionp = max(session) if session else None
|
|
2317
|
+
# ...but only the WEEKLY half of that rule is about a missing answer. The two kinds
|
|
2318
|
+
# of bucket go silent for opposite reasons, and 2026-09-22 cost a day of picks to
|
|
2319
|
+
# the difference. A weekly window always exists — it is a fixed calendar week,
|
|
2320
|
+
# running whether or not the account is — so a weekly bucket with nothing to say is
|
|
2321
|
+
# an endpoint that DECLINED, and inventing a number for it is the 2026-09-04 bug.
|
|
2322
|
+
# The 5h SESSION window only exists while it is OPEN: leave an account alone for
|
|
2323
|
+
# five hours and there is no window left to describe, so the endpoint answers
|
|
2324
|
+
# `percent: 0, resets_at: null` — not silence but "nothing has been used". An
|
|
2325
|
+
# exhausted session is never silent: a spent 5h bucket ALWAYS carries the window it
|
|
2326
|
+
# resets in (the live pool on 2026-09-22 — every session bucket above 0% had a real
|
|
2327
|
+
# resets_at, every silent one belonged to an idle account), so a silent session
|
|
2328
|
+
# cannot be hiding a full one. Recording it as UNKNOWN is what broke selection:
|
|
2329
|
+
# pick_best's quota_known rule needs BOTH readings, so an IDLE account — precisely
|
|
2330
|
+
# the one with the most headroom — was scored weekly=100 and dropped out of the
|
|
2331
|
+
# band, leaving only the busy accounts that still held an open 5h window. From
|
|
2332
|
+
# selection.log at 2026-09-22T12:37:36Z: band-count=1, and that one band member was
|
|
2333
|
+
# acct-17 at 95% weekly, while acct-13 and acct-14 sat at 0% and unrankable.
|
|
2334
|
+
# So: a session bucket that said nothing inside a document that DID answer reads 0.
|
|
2335
|
+
# A document where NOTHING answered still writes no signals at all (see below) —
|
|
2336
|
+
# the 2026-09-04 all-zero shape stays unknown, which is the whole point.
|
|
2337
|
+
session_measured = bool(session)
|
|
2338
|
+
if sessionp is None and live:
|
|
2339
|
+
sessionp = 0
|
|
2308
2340
|
# How long weekly_percent keeps meaning something. A weekly bucket only ever RISES
|
|
2309
2341
|
# until its reset, so before that moment a stale percent is still a valid lower
|
|
2310
2342
|
# bound and the shim can rank on it when nothing fresher exists; after it, the
|
|
@@ -2323,6 +2355,16 @@ for acct in manifest.get('accounts', []):
|
|
|
2323
2355
|
out['weekly_percent'] = weeklyp
|
|
2324
2356
|
if sessionp is not None:
|
|
2325
2357
|
out['session_percent'] = sessionp
|
|
2358
|
+
if sessionp is not None and not session_measured:
|
|
2359
|
+
# Ranking may use this 0 (that is the whole point — an idle account has to get
|
|
2360
|
+
# back into the band), but NOTHING may treat it as proof of recovery. 2026-09-22,
|
|
2361
|
+
# acct-14: a client:five_hour marker written from a real 429 at 13:06:31Z, and
|
|
2362
|
+
# telemetry fetched at 13:04:16Z — WHILE the account was being rejected — still
|
|
2363
|
+
# reported the session bucket `percent: 0, resets_at: null`. So a silent session
|
|
2364
|
+
# bucket does NOT prove an idle account; the endpoint simply may not report the
|
|
2365
|
+
# 5h window at all. The marker was cleared anyway, `--resume` landed straight on
|
|
2366
|
+
# it, and the run was rejected inside a minute. Recovery has to be MEASURED.
|
|
2367
|
+
out['session_inferred'] = True
|
|
2326
2368
|
if weeklyp is not None:
|
|
2327
2369
|
# Written with weekly_percent or not at all: the horizon describes THAT
|
|
2328
2370
|
# reading, and the shim's degraded path needs both or neither.
|
|
@@ -2424,6 +2466,13 @@ for acct in manifest.get('accounts', []):
|
|
|
2424
2466
|
keep = True
|
|
2425
2467
|
elif active and is_client and weekly_marker(txt):
|
|
2426
2468
|
keep = True
|
|
2469
|
+
elif active and is_client and session_marker(txt) and not session_measured:
|
|
2470
|
+
# Same rule as the weekly one, one window down: a rejection the CLIENT
|
|
2471
|
+
# reported is only disproved by telemetry that actually looked at the
|
|
2472
|
+
# window it names. `max_percent` here comes from the WEEKLY buckets
|
|
2473
|
+
# when the session bucket is silent, and clearing a 5h marker on it is
|
|
2474
|
+
# the cross-signal borrow the aggregation above refuses to make.
|
|
2475
|
+
keep = True
|
|
2427
2476
|
elif active and ('reason=error-cooldown' in txt
|
|
2428
2477
|
or (is_client and recent_client)):
|
|
2429
2478
|
keep = True
|
package/bin/codex-accounts
CHANGED
|
@@ -1154,6 +1154,14 @@ def marker_bucket(txt):
|
|
|
1154
1154
|
return part[7:]
|
|
1155
1155
|
return ''
|
|
1156
1156
|
|
|
1157
|
+
def session_marker(txt):
|
|
1158
|
+
# The self-healing ~5h window as each client names it: codex labels a short window
|
|
1159
|
+
# `client:5h`, claude's own type is `five_hour`, and a bare `session` covers the
|
|
1160
|
+
# writer's own marker. Kept identical to bin/claude-accounts' copy.
|
|
1161
|
+
b = marker_bucket(txt).lower()
|
|
1162
|
+
return 'five_hour' in b or '5h' in b or 'session' in b
|
|
1163
|
+
|
|
1164
|
+
|
|
1157
1165
|
def weekly_marker(txt):
|
|
1158
1166
|
# Matched the way both shims match it (bin/claude ~303, same token list): the
|
|
1159
1167
|
# claude client writes client:seven_day / client:seven_day_opus, and the codex
|
|
@@ -1585,6 +1593,23 @@ for acct in manifest.get('accounts', []):
|
|
|
1585
1593
|
maxp = max([b['percent'] for b in live] or [0])
|
|
1586
1594
|
weeklyp = max(weekly) if weekly else None
|
|
1587
1595
|
sessionp = max(session) if session else None
|
|
1596
|
+
# ...but only the WEEKLY half of that rule is about a missing answer — same
|
|
1597
|
+
# asymmetry the claude writer documents, and codex feels it harder. A durable
|
|
1598
|
+
# window always exists, so a silent one is an endpoint that DECLINED. A ~5h session
|
|
1599
|
+
# window only exists while it is OPEN, and codex does not even keep a placeholder
|
|
1600
|
+
# for a closed one: an idle account's payload carries the 7d window and NO session
|
|
1601
|
+
# window whatsoever, so `session` here is empty for every quiet account. That made
|
|
1602
|
+
# session_percent permanently absent across this pool, and pick_best's quota_known
|
|
1603
|
+
# rule needs BOTH readings — so EVERY codex account scored unknown, bestw never left
|
|
1604
|
+
# 101, and the band degenerated to the all-gated tie: codex has been picking
|
|
1605
|
+
# uniformly at RANDOM rather than by headroom (selection.log: `session=?%` on every
|
|
1606
|
+
# ranked line). A session window that is closed has nothing in it, and an exhausted
|
|
1607
|
+
# one is never silent — it reports at/near 100% with the reset it is waiting on.
|
|
1608
|
+
# So a missing or silent session reads 0 whenever the payload answered at all; a
|
|
1609
|
+
# payload where NOTHING answered still writes no signals and stays unknown below.
|
|
1610
|
+
session_measured = bool(session)
|
|
1611
|
+
if sessionp is None and live:
|
|
1612
|
+
sessionp = 0
|
|
1588
1613
|
try:
|
|
1589
1614
|
reset_result, reset_view = refresh_reset_credits(d, aid, maxp, data, url, headers, say, int(now))
|
|
1590
1615
|
except Exception as e:
|
|
@@ -1603,6 +1628,13 @@ for acct in manifest.get('accounts', []):
|
|
|
1603
1628
|
out['weekly_percent'] = weeklyp
|
|
1604
1629
|
if sessionp is not None:
|
|
1605
1630
|
out['session_percent'] = sessionp
|
|
1631
|
+
if sessionp is not None and not session_measured:
|
|
1632
|
+
# Ranking may use this 0 — an idle account has to get back into the band — but
|
|
1633
|
+
# nothing may read it as proof of recovery. codex reports no window at all for a
|
|
1634
|
+
# closed one, so silence here is the NORMAL shape and says nothing whatsoever
|
|
1635
|
+
# about a 429 the client actually received. See the claude writer for the live
|
|
1636
|
+
# case that proved it (acct-14, 2026-09-22).
|
|
1637
|
+
out['session_inferred'] = True
|
|
1606
1638
|
if not live:
|
|
1607
1639
|
# Nothing usable in the entire payload. Keep the diagnostics (fetched_at,
|
|
1608
1640
|
# source, plan, the raw buckets) and write NONE of the percent signals: a
|
|
@@ -1711,6 +1743,13 @@ for acct in manifest.get('accounts', []):
|
|
|
1711
1743
|
keep = True
|
|
1712
1744
|
elif active and is_client and weekly_marker(txt):
|
|
1713
1745
|
keep = True
|
|
1746
|
+
elif active and is_client and session_marker(txt) and not session_measured:
|
|
1747
|
+
# Same rule one window down: a rejection the CLIENT reported is only
|
|
1748
|
+
# disproved by telemetry that looked at the window it names. With the
|
|
1749
|
+
# session window silent, max_percent is the WEEKLY peak, and clearing
|
|
1750
|
+
# a 5h marker on it is the cross-signal borrow the aggregation above
|
|
1751
|
+
# refuses to make.
|
|
1752
|
+
keep = True
|
|
1714
1753
|
elif active and ('reason=error-cooldown' in txt
|
|
1715
1754
|
or (is_client and recent_client)):
|
|
1716
1755
|
keep = True
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/tests/run-tests.sh
CHANGED
|
@@ -2458,10 +2458,18 @@ done
|
|
|
2458
2458
|
&& t_ok "an account with no weekly reading never enters the band (0 of 10 picks)" \
|
|
2459
2459
|
|| t_fail "session-only ranking" "the weekly-less account took $nd_sess_hits of 10 picks"
|
|
2460
2460
|
|
|
2461
|
-
# The mirror image
|
|
2462
|
-
#
|
|
2463
|
-
#
|
|
2464
|
-
#
|
|
2461
|
+
# The mirror image — a weekly reading whose 5h bucket has nothing to say — is NOT the
|
|
2462
|
+
# same defect, and treating it as one broke selection on 2026-09-22. A weekly window
|
|
2463
|
+
# always exists (a fixed calendar week, running whether or not the account is), so a
|
|
2464
|
+
# silent weekly bucket is an endpoint that DECLINED. The 5h window only exists while it
|
|
2465
|
+
# is OPEN, so a silent session bucket is an account that has simply been IDLE — and an
|
|
2466
|
+
# exhausted session is never silent: a spent 5h bucket always reports ~100% with the
|
|
2467
|
+
# reset it is waiting on. Recording idle as UNKNOWN cost the pool its entire band,
|
|
2468
|
+
# because quota_known needs BOTH readings: the accounts with the MOST headroom
|
|
2469
|
+
# (acct-13/acct-14, 0% weekly) were scored 100 and dropped out, leaving acct-17 at 95%
|
|
2470
|
+
# weekly as the only band member — selection.log 2026-09-22T12:37:36Z, band-count=1,
|
|
2471
|
+
# four picks in a row onto an account already out of weekly headroom. A closed window
|
|
2472
|
+
# holds no usage: it reads 0.
|
|
2465
2473
|
cat > "$WORK/usage-weekly-only.json" <<'EOF'
|
|
2466
2474
|
{"limits":[
|
|
2467
2475
|
{"kind":"session","group":"session","percent":0,"resets_at":null,"scope":null},
|
|
@@ -2476,10 +2484,33 @@ assert 'no_data' not in d, d
|
|
|
2476
2484
|
assert (d['weekly_percent'], d['max_percent']) == (37, 37), d
|
|
2477
2485
|
assert d['weekly_resets_epoch'] == calendar.timegm(
|
|
2478
2486
|
time.strptime("2099-01-05T00:00:00", "%Y-%m-%dT%H:%M:%S")), d
|
|
2479
|
-
|
|
2487
|
+
# The 5h window is CLOSED, not undocumented, so it holds no usage. max_percent is still
|
|
2488
|
+
# taken over the INFORMATIVE buckets only, so this idle 0 never becomes a bucket reading.
|
|
2489
|
+
assert d['session_percent'] == 0, d
|
|
2480
2490
|
EOF
|
|
2481
|
-
[ $? -eq 0 ] && t_ok "
|
|
2482
|
-
|| t_fail "
|
|
2491
|
+
[ $? -eq 0 ] && t_ok "an idle 5h bucket beside a real weekly one records session_percent 0" \
|
|
2492
|
+
|| t_fail "idle-session signals" "see $ND/acct-01/limits.json"
|
|
2493
|
+
|
|
2494
|
+
# A payload with NO session bucket at ALL is the same story told by omission — the shape
|
|
2495
|
+
# every codex account produces while it is quiet. It must rank, not vanish.
|
|
2496
|
+
cat > "$WORK/usage-no-session-bucket.json" <<'EOF'
|
|
2497
|
+
{"limits":[
|
|
2498
|
+
{"kind":"weekly_all","group":"weekly","percent":37,"resets_at":"2099-01-05T00:00:00+00:00","scope":null}
|
|
2499
|
+
]}
|
|
2500
|
+
EOF
|
|
2501
|
+
ndlimits "$WORK/usage-no-session-bucket.json" --quiet >/dev/null
|
|
2502
|
+
python3 - "$ND/acct-01/limits.json" <<'EOF'
|
|
2503
|
+
import json, sys
|
|
2504
|
+
d = json.load(open(sys.argv[1]))
|
|
2505
|
+
assert 'no_data' not in d, d
|
|
2506
|
+
assert (d['weekly_percent'], d['session_percent'], d['max_percent']) == (37, 0, 37), d
|
|
2507
|
+
EOF
|
|
2508
|
+
[ $? -eq 0 ] && t_ok "a payload with no session bucket at all still records session_percent 0" \
|
|
2509
|
+
|| t_fail "absent-session signals" "see $ND/acct-01/limits.json"
|
|
2510
|
+
|
|
2511
|
+
# ...and that account RANKS: idle plus the better weekly must beat a busier rival — the
|
|
2512
|
+
# pick the pool was losing. acct-02 is worse on weekly (80 vs 37) and clears the gate
|
|
2513
|
+
# too, so the band alone separates them.
|
|
2483
2514
|
ndl 80 10 80 > "$ND/acct-02/limits.json"
|
|
2484
2515
|
rm -f "$ND/.pick-seq" "$ND"/acct-0*/.last-pick "$ND"/acct-0*/.limited
|
|
2485
2516
|
: > "$ND/selection.log"
|
|
@@ -2487,12 +2518,96 @@ nd_wk_hits=0
|
|
|
2487
2518
|
for _ in $(seq 1 6); do
|
|
2488
2519
|
case "$(CLAUDE_ACCOUNTS_ROOT="$ND" claude 2>&1)" in *CFG=acct-01*) nd_wk_hits=$((nd_wk_hits+1)) ;; esac
|
|
2489
2520
|
done
|
|
2490
|
-
[ "$nd_wk_hits" = "
|
|
2491
|
-
&& t_ok "an
|
|
2492
|
-
|| t_fail "
|
|
2493
|
-
grep -q "acct-
|
|
2494
|
-
&& t_ok "the log shows
|
|
2495
|
-
|| t_fail "
|
|
2521
|
+
[ "$nd_wk_hits" = "6" ] \
|
|
2522
|
+
&& t_ok "an idle account with the better weekly takes every pick (6 of 6)" \
|
|
2523
|
+
|| t_fail "idle-session ranking" "the idle account took $nd_wk_hits of 6"
|
|
2524
|
+
grep -q "acct-01 weekly=37% session=0% band=30 band-count=1 session-gate=50 session-ok=2" "$ND/selection.log" \
|
|
2525
|
+
&& t_ok "the log shows both clearing the gate and the idle account alone in the band" \
|
|
2526
|
+
|| t_fail "idle-session log" "$(tail -1 "$ND/selection.log" 2>/dev/null)"
|
|
2527
|
+
|
|
2528
|
+
# The guard that must NOT move: a document where NOTHING answered is still unknown. The
|
|
2529
|
+
# all-zero 2026-09-04 shape has no informative bucket anywhere, so it writes no signals
|
|
2530
|
+
# at all and never picks up a fabricated session 0 on the way out.
|
|
2531
|
+
ndlimits "$WORK/usage-allzero.json" --quiet >/dev/null
|
|
2532
|
+
python3 - "$ND/acct-01/limits.json" <<'EOF'
|
|
2533
|
+
import json, sys
|
|
2534
|
+
d = json.load(open(sys.argv[1]))
|
|
2535
|
+
assert d.get('no_data') is True, d
|
|
2536
|
+
for k in ('max_percent', 'weekly_percent', 'session_percent', 'weekly_resets_epoch'):
|
|
2537
|
+
assert k not in d, (k, d)
|
|
2538
|
+
EOF
|
|
2539
|
+
[ $? -eq 0 ] && t_ok "an all-zero payload still writes no session_percent (2026-09-04 holds)" \
|
|
2540
|
+
|| t_fail "allzero still unknown" "see $ND/acct-01/limits.json"
|
|
2541
|
+
|
|
2542
|
+
# ---- 16-idle-marker. an INFERRED session 0 may rank, but it may never unpark ---------
|
|
2543
|
+
# 2026-09-22, the second half of the same day: with idle accounts ranking again, the
|
|
2544
|
+
# operator's `claude --resume` landed on acct-14 and was rejected inside a minute.
|
|
2545
|
+
# selection.log: "acct-14 client limit cleared by newer telemetry (0%)" one second before
|
|
2546
|
+
# the pick, and "LIMITED by its own session (five_hour)" 54s after it. acct-14 held a
|
|
2547
|
+
# client:five_hour marker from a REAL 429, and its telemetry — fetched at 13:04:16Z,
|
|
2548
|
+
# WHILE the client was being rejected — still reported the session bucket `percent: 0,
|
|
2549
|
+
# resets_at: null`. So a silent session bucket does NOT prove an idle account: the
|
|
2550
|
+
# endpoint may simply never report the 5h window. Recovery has to be MEASURED. The
|
|
2551
|
+
# clearing rule read max_percent, which is the WEEKLY peak when the session bucket is
|
|
2552
|
+
# silent — the cross-signal borrow the aggregation itself refuses to make.
|
|
2553
|
+
mk_client_marker() { # mk_client_marker <acct dir> <bucket> <age seconds> <reset in>
|
|
2554
|
+
python3 - "$1" "$2" "$3" "$4" <<'PJ'
|
|
2555
|
+
import sys, time, os
|
|
2556
|
+
d, bucket, age, ahead = sys.argv[1], sys.argv[2], int(sys.argv[3]), int(sys.argv[4])
|
|
2557
|
+
now = int(time.time())
|
|
2558
|
+
marked = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(now - age))
|
|
2559
|
+
with open(os.path.join(d, '.limited'), 'w') as f:
|
|
2560
|
+
f.write(f'{now + ahead}\n'
|
|
2561
|
+
f'bucket=client:{bucket} percent=100 marked_at={marked} '
|
|
2562
|
+
f'reason=client-rate-limit\n')
|
|
2563
|
+
PJ
|
|
2564
|
+
}
|
|
2565
|
+
# the acct-14 shape: a live 5h rejection, and telemetry whose session bucket is silent
|
|
2566
|
+
mk_client_marker "$ND/acct-01" five_hour 1200 7200
|
|
2567
|
+
ndlimits "$WORK/usage-weekly-only.json" --quiet >/dev/null
|
|
2568
|
+
{ [ -f "$ND/acct-01/.limited" ] \
|
|
2569
|
+
&& python3 -c "import json,sys; d=json.load(open('$ND/acct-01/limits.json')); sys.exit(0 if d.get('session_inferred') is True and d['session_percent'] == 0 else 1)"; } \
|
|
2570
|
+
&& t_ok "an inferred session 0 cannot clear a client:five_hour marker (writer)" \
|
|
2571
|
+
|| t_fail "inferred unpark (writer)" "marker gone, or session_inferred not recorded"
|
|
2572
|
+
|
|
2573
|
+
# ...and the SHIM must agree, because it is the shim that logged the clear before the
|
|
2574
|
+
# bad pick. The rival is WORSE on weekly and must still take every launch.
|
|
2575
|
+
ndl 60 10 60 > "$ND/acct-02/limits.json"
|
|
2576
|
+
rm -f "$ND/.pick-seq" "$ND"/acct-0*/.last-pick "$ND/acct-02/.limited"
|
|
2577
|
+
: > "$ND/selection.log"
|
|
2578
|
+
nd_park_hits=0
|
|
2579
|
+
for _ in $(seq 1 6); do
|
|
2580
|
+
case "$(CLAUDE_ACCOUNTS_ROOT="$ND" claude 2>&1)" in *CFG=acct-01*) nd_park_hits=$((nd_park_hits+1)) ;; esac
|
|
2581
|
+
done
|
|
2582
|
+
{ [ "$nd_park_hits" = "0" ] && [ -f "$ND/acct-01/.limited" ] \
|
|
2583
|
+
&& ! grep -q "cleared by newer telemetry" "$ND/selection.log"; } \
|
|
2584
|
+
&& t_ok "the shim never unparks a 5h marker on an inferred reading (0 of 6 picks)" \
|
|
2585
|
+
|| t_fail "inferred unpark (shim)" \
|
|
2586
|
+
"the parked account took $nd_park_hits of 6; $(tail -1 "$ND/selection.log" 2>/dev/null)"
|
|
2587
|
+
|
|
2588
|
+
# The #22 behaviour that must SURVIVE: a MEASURED low session reading still clears a 5h
|
|
2589
|
+
# marker. That window self-heals in hours, which is why #22 (2026-09-03) had to clear it
|
|
2590
|
+
# — this fix narrows the evidence, it does not park an account until its reset.
|
|
2591
|
+
mk_client_marker "$ND/acct-01" five_hour 1200 7200
|
|
2592
|
+
cat > "$WORK/usage-measured-session.json" <<'EOF'
|
|
2593
|
+
{"limits":[
|
|
2594
|
+
{"kind":"session","group":"session","percent":5,"resets_at":"2099-01-01T00:00:00+00:00","scope":null},
|
|
2595
|
+
{"kind":"weekly_all","group":"weekly","percent":37,"resets_at":"2099-01-05T00:00:00+00:00","scope":null}
|
|
2596
|
+
]}
|
|
2597
|
+
EOF
|
|
2598
|
+
ndlimits "$WORK/usage-measured-session.json" --quiet >/dev/null
|
|
2599
|
+
{ [ ! -f "$ND/acct-01/.limited" ] \
|
|
2600
|
+
&& python3 -c "import json,sys; d=json.load(open('$ND/acct-01/limits.json')); sys.exit(0 if 'session_inferred' not in d and d['session_percent'] == 5 else 1)"; } \
|
|
2601
|
+
&& t_ok "a MEASURED low session reading still clears a five_hour marker (#22 holds)" \
|
|
2602
|
+
|| t_fail "measured recovery" "see $ND/acct-01/.limited"
|
|
2603
|
+
|
|
2604
|
+
# A WEEKLY marker is untouched by any of this — it was already un-clearable.
|
|
2605
|
+
mk_client_marker "$ND/acct-01" seven_day 1200 7200
|
|
2606
|
+
ndlimits "$WORK/usage-measured-session.json" --quiet >/dev/null
|
|
2607
|
+
[ -f "$ND/acct-01/.limited" ] \
|
|
2608
|
+
&& t_ok "a weekly marker still outlives even a measured reading (2026-09-04 holds)" \
|
|
2609
|
+
|| t_fail "weekly marker cleared" "see $ND/acct-01"
|
|
2610
|
+
rm -f "$ND"/acct-0*/.limited "$ND"/acct-0*/.client-limit-cleared
|
|
2496
2611
|
|
|
2497
2612
|
# ---- 16-nodata-blind. a fresh timestamp is not a usable reading ---------------------
|
|
2498
2613
|
# The third face of the same defect: blindness was judged on fetched_at alone, so a pool
|
|
@@ -5872,6 +5987,13 @@ done
|
|
|
5872
5987
|
&& t_ok "codex: an account with no weekly reading never enters the band (0 of 10 picks)" \
|
|
5873
5988
|
|| t_fail "codex session-only ranking" "the weekly-less account took $cs_hits of 10 picks"
|
|
5874
5989
|
|
|
5990
|
+
# The mirror image — a weekly reading whose ~5h window has nothing to say — is NOT the
|
|
5991
|
+
# same defect, and codex feels it hardest: an idle account's payload carries the 7d
|
|
5992
|
+
# window and no usable session window at all, so session_percent went permanently
|
|
5993
|
+
# absent across the whole pool. quota_known needs BOTH readings, so EVERY codex account
|
|
5994
|
+
# scored unknown, bestw never left 101, and the band collapsed into the all-gated tie —
|
|
5995
|
+
# codex picked uniformly at RANDOM instead of by headroom (selection.log 2026-09-22:
|
|
5996
|
+
# `session=?%` on every ranked line). A closed window holds no usage: it reads 0.
|
|
5875
5997
|
cat > "$WORK/cx-usage-weekly-only.json" <<EOF
|
|
5876
5998
|
{"email":"nd@cx","plan_type":"pro",
|
|
5877
5999
|
"rate_limit":{"allowed":true,"limit_reached":false,
|
|
@@ -5885,13 +6007,32 @@ import json, sys
|
|
|
5885
6007
|
d = json.load(open(sys.argv[1]))
|
|
5886
6008
|
assert 'no_data' not in d, d
|
|
5887
6009
|
assert (d['weekly_percent'], d['max_percent']) == (37, 37), d
|
|
5888
|
-
|
|
6010
|
+
# The ~5h window is CLOSED, not undocumented, so it holds no usage. max_percent is still
|
|
6011
|
+
# taken over the INFORMATIVE windows only, so this idle 0 never becomes a reading.
|
|
6012
|
+
assert d['session_percent'] == 0, d
|
|
5889
6013
|
EOF
|
|
5890
|
-
[ $? -eq 0 ] && t_ok "codex:
|
|
5891
|
-
|| t_fail "codex
|
|
5892
|
-
|
|
5893
|
-
#
|
|
5894
|
-
|
|
6014
|
+
[ $? -eq 0 ] && t_ok "codex: an idle 5h window beside a real weekly one records session_percent 0" \
|
|
6015
|
+
|| t_fail "codex idle-session signals" "see $CD/acct-01/limits.json"
|
|
6016
|
+
|
|
6017
|
+
# The same story told by omission: no session window in the payload at all.
|
|
6018
|
+
cat > "$WORK/cx-usage-no-session.json" <<EOF
|
|
6019
|
+
{"email":"nd@cx","plan_type":"pro",
|
|
6020
|
+
"rate_limit":{"allowed":true,"limit_reached":false,
|
|
6021
|
+
"secondary_window":{"used_percent":37,"limit_window_seconds":604800,"reset_at":$((now+90000))}},
|
|
6022
|
+
"additional_rate_limits":[]}
|
|
6023
|
+
EOF
|
|
6024
|
+
cdlimits "$WORK/cx-usage-no-session.json" --quiet >/dev/null
|
|
6025
|
+
python3 - "$CD/acct-01/limits.json" <<'EOF'
|
|
6026
|
+
import json, sys
|
|
6027
|
+
d = json.load(open(sys.argv[1]))
|
|
6028
|
+
assert 'no_data' not in d, d
|
|
6029
|
+
assert (d['weekly_percent'], d['session_percent'], d['max_percent']) == (37, 0, 37), d
|
|
6030
|
+
EOF
|
|
6031
|
+
[ $? -eq 0 ] && t_ok "codex: a payload with no session window at all records session_percent 0" \
|
|
6032
|
+
|| t_fail "codex absent-session signals" "see $CD/acct-01/limits.json"
|
|
6033
|
+
|
|
6034
|
+
# ...and it RANKS: idle plus the better weekly (37) must beat busier rivals (80), which
|
|
6035
|
+
# is the pick this pool was losing on every single invocation.
|
|
5895
6036
|
cdl 80 10 80 > "$CD/acct-02/limits.json"
|
|
5896
6037
|
cdl 80 10 80 > "$CD/acct-03/limits.json"
|
|
5897
6038
|
rm -f "$CD/.pick-seq" "$CD"/acct-0*/.last-pick
|
|
@@ -5900,12 +6041,53 @@ cwk_hits=0
|
|
|
5900
6041
|
for _ in $(seq 1 6); do
|
|
5901
6042
|
case "$(CODEX_ACCOUNTS_ROOT="$CD" codex 2>&1)" in *CFG=acct-01*) cwk_hits=$((cwk_hits+1)) ;; esac
|
|
5902
6043
|
done
|
|
5903
|
-
[ "$cwk_hits" = "
|
|
5904
|
-
&& t_ok "codex: an
|
|
5905
|
-
|| t_fail "codex
|
|
5906
|
-
grep -q "band=30 band-count=
|
|
5907
|
-
&& t_ok "codex: the
|
|
5908
|
-
|| t_fail "codex
|
|
6044
|
+
[ "$cwk_hits" = "6" ] \
|
|
6045
|
+
&& t_ok "codex: an idle account with the better weekly takes every pick (6 of 6)" \
|
|
6046
|
+
|| t_fail "codex idle-session ranking" "the idle account took $cwk_hits of 6"
|
|
6047
|
+
grep -q "acct-01 weekly=37% session=0% band=30 band-count=1 session-gate=50 session-ok=3" "$CD/selection.log" \
|
|
6048
|
+
&& t_ok "codex: all three clear the gate and the idle account is alone in the band" \
|
|
6049
|
+
|| t_fail "codex idle-session log" "$(tail -1 "$CD/selection.log" 2>/dev/null)"
|
|
6050
|
+
|
|
6051
|
+
# The guard that must NOT move: a payload where NOTHING answered stays unknown, and
|
|
6052
|
+
# never picks up a fabricated session 0 on the way out.
|
|
6053
|
+
cdlimits "$WORK/cx-usage-allzero.json" --quiet >/dev/null
|
|
6054
|
+
python3 - "$CD/acct-01/limits.json" <<'EOF'
|
|
6055
|
+
import json, sys
|
|
6056
|
+
d = json.load(open(sys.argv[1]))
|
|
6057
|
+
assert d.get('no_data') is True, d
|
|
6058
|
+
for k in ('max_percent', 'weekly_percent', 'session_percent'):
|
|
6059
|
+
assert k not in d, (k, d)
|
|
6060
|
+
EOF
|
|
6061
|
+
[ $? -eq 0 ] && t_ok "codex: an all-zero payload still writes no session_percent (2026-09-04 holds)" \
|
|
6062
|
+
|| t_fail "codex allzero still unknown" "see $CD/acct-01/limits.json"
|
|
6063
|
+
|
|
6064
|
+
# ...and the same guard one window down: an INFERRED session 0 may rank, never unpark.
|
|
6065
|
+
# codex reports no window at all for a closed one, so silence is its NORMAL shape and
|
|
6066
|
+
# says nothing about a 429 the client actually received. codex's only telemetry-driven
|
|
6067
|
+
# clear lives here in `limits` (the shim has none — marker_active waits for the reset),
|
|
6068
|
+
# so this is the single place the rule has to hold.
|
|
6069
|
+
mk_client_marker "$CD/acct-01" 5h 1200 7200
|
|
6070
|
+
cdlimits "$WORK/cx-usage-weekly-only.json" --quiet >/dev/null
|
|
6071
|
+
{ [ -f "$CD/acct-01/.limited" ] \
|
|
6072
|
+
&& python3 -c "import json,sys; d=json.load(open('$CD/acct-01/limits.json')); sys.exit(0 if d.get('session_inferred') is True and d['session_percent'] == 0 else 1)"; } \
|
|
6073
|
+
&& t_ok "codex: an inferred session 0 cannot clear a client:5h marker" \
|
|
6074
|
+
|| t_fail "codex inferred unpark" "marker gone, or session_inferred not recorded"
|
|
6075
|
+
|
|
6076
|
+
# ...while a MEASURED short-window reading still clears it (#22 holds on codex too).
|
|
6077
|
+
mk_client_marker "$CD/acct-01" 5h 1200 7200
|
|
6078
|
+
cat > "$WORK/cx-usage-measured-session.json" <<EOF
|
|
6079
|
+
{"email":"nd@cx","plan_type":"pro",
|
|
6080
|
+
"rate_limit":{"allowed":true,"limit_reached":false,
|
|
6081
|
+
"primary_window":{"used_percent":5,"limit_window_seconds":18000,"reset_at":$((now+9000))},
|
|
6082
|
+
"secondary_window":{"used_percent":37,"limit_window_seconds":604800,"reset_at":$((now+90000))}},
|
|
6083
|
+
"additional_rate_limits":[]}
|
|
6084
|
+
EOF
|
|
6085
|
+
cdlimits "$WORK/cx-usage-measured-session.json" --quiet >/dev/null
|
|
6086
|
+
{ [ ! -f "$CD/acct-01/.limited" ] \
|
|
6087
|
+
&& python3 -c "import json,sys; d=json.load(open('$CD/acct-01/limits.json')); sys.exit(0 if 'session_inferred' not in d and d['session_percent'] == 5 else 1)"; } \
|
|
6088
|
+
&& t_ok "codex: a MEASURED low session reading still clears a 5h marker (#22 holds)" \
|
|
6089
|
+
|| t_fail "codex measured recovery" "see $CD/acct-01/.limited"
|
|
6090
|
+
rm -f "$CD"/acct-0*/.limited "$CD"/acct-0*/.client-limit-cleared
|
|
5909
6091
|
rm -f "$CD"/acct-0*/.limited
|
|
5910
6092
|
|
|
5911
6093
|
# ---- C14. oauth refresh via the token endpoint --------------------------------------
|