claude-multiacc 2.0.34 → 2.0.36
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 +31 -0
- package/bin/claude +86 -1
- package/bin/claude-accounts +180 -10
- package/bin/codex-accounts +46 -1
- package/lib/__pycache__/audit.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
- package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
- package/lib/__pycache__/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/claude_reset.py +673 -0
- package/lib/common.sh +3 -1
- package/lib/report.py +18 -0
- package/package.json +1 -1
- package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_claude_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
- package/tests/run-tests.sh +206 -0
- package/tests/test_claude_reset.py +622 -0
- package/tests/test_codex_reset.py +35 -0
package/lib/common.sh
CHANGED
|
@@ -36,7 +36,9 @@ else
|
|
|
36
36
|
ACC_ROOT="${CLAUDE_ACCOUNTS_ROOT:-${CLAUDE_ACCOUNTS_DIR:-$HOME/.claude-accounts}}"
|
|
37
37
|
DEFAULT_ACC_ROOT="$HOME/.claude-accounts"
|
|
38
38
|
DEFAULT_SERVER_ROOT="/root/.claude-accounts"
|
|
39
|
-
|
|
39
|
+
# `?cedar_ember=1&skip_spend=1` is Claude Code's own /limit-reset read: the same
|
|
40
|
+
# usage payload plus the limit-reset status block (lib/claude_reset.py), in ONE call.
|
|
41
|
+
USAGE_URL="${CLAUDE_MULTIACC_USAGE_URL:-https://api.anthropic.com/api/oauth/usage?cedar_ember=1&skip_spend=1}"
|
|
40
42
|
AUDIT_PY="$LIB_DIR/audit.py"
|
|
41
43
|
PROVIDER_CLI="claude-accounts"
|
|
42
44
|
SYNC_TARGET_ENV="${CLAUDE_MULTIACC_SYNC_TARGET:-${MULTIACC_SYNC_TARGET:-}}"
|
package/lib/report.py
CHANGED
|
@@ -47,6 +47,7 @@ import time
|
|
|
47
47
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
48
48
|
import keychain # noqa: E402
|
|
49
49
|
from audit import token_verified # noqa: E402
|
|
50
|
+
from claude_reset import marker_superseded, record_from # noqa: E402
|
|
50
51
|
|
|
51
52
|
SCHEMA = 'claude-multiacc/pool.v1'
|
|
52
53
|
|
|
@@ -246,6 +247,12 @@ def _usage(d, stale_after):
|
|
|
246
247
|
reset_stamp = _iso(lim.get('reset_credits_fetched_at'))
|
|
247
248
|
if type(reset_count) is int and 0 <= reset_count <= 1_000_000 and reset_stamp:
|
|
248
249
|
out.update(reset_credits_available=reset_count, reset_credits_fetched_at=reset_stamp)
|
|
250
|
+
# A confirmed limit reset (claude): when it happened and which limit types it refilled.
|
|
251
|
+
# Every park written before it for one of those windows is lifted — here, in the shim,
|
|
252
|
+
# and (from this field) in the app-robot panel. Emitted only while the writer carries it.
|
|
253
|
+
record = record_from(lim, time.time())
|
|
254
|
+
if record:
|
|
255
|
+
out.update(reset_redeemed_at=_iso(record[0]), reset_cleared=list(record[1]))
|
|
249
256
|
try:
|
|
250
257
|
out['age_seconds'] = max(0, int(time.time() - float(fetched)))
|
|
251
258
|
except (TypeError, ValueError):
|
|
@@ -368,6 +375,17 @@ def _account_row(root, a, aid, d, provider, jwt_claims, audit_account, now, kind
|
|
|
368
375
|
cclass, cdetail = _claude_credentials(d)
|
|
369
376
|
reset_epoch, marker_detail = _marker(os.path.join(d, '.limited'))
|
|
370
377
|
limited = reset_epoch > now or (reset_epoch == 0 and os.path.isfile(os.path.join(d, '.limited')))
|
|
378
|
+
if limited and provider == 'claude':
|
|
379
|
+
# The shim's reset_supersedes_marker: a park from before a confirmed limit reset,
|
|
380
|
+
# for a window it refilled, no longer binds — even as a stale copy on a peer.
|
|
381
|
+
try:
|
|
382
|
+
with open(os.path.join(d, '.limited'), errors='replace') as handle:
|
|
383
|
+
text = handle.read(4096)
|
|
384
|
+
if marker_superseded(text, record_from(_load_json(os.path.join(d, 'limits.json')),
|
|
385
|
+
time.time())):
|
|
386
|
+
limited = False
|
|
387
|
+
except OSError:
|
|
388
|
+
pass
|
|
371
389
|
status = st['state']
|
|
372
390
|
if status == 'ok':
|
|
373
391
|
status = 'limited' if limited else 'active'
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/tests/run-tests.sh
CHANGED
|
@@ -2271,6 +2271,107 @@ grep -q "acct-01 client limit cleared by newer telemetry (5%)" "$ND/selection.lo
|
|
|
2271
2271
|
&& t_ok "the five_hour recovery still logs the reading it acted on" \
|
|
2272
2272
|
|| t_fail "five_hour client marker" "selection.log: $(tail -2 "$ND/selection.log" 2>/dev/null | tr '\n' ' ')"
|
|
2273
2273
|
|
|
2274
|
+
# ---- 16-limit-reset-fleet. a confirmed limit reset lifts every OLDER park, fleet-wide --
|
|
2275
|
+
# The writer that redeemed a reset deletes its own marker, but peers are pushed `.limited`
|
|
2276
|
+
# files and never their deletion, and the panel re-writes its verdicts: so the reset
|
|
2277
|
+
# travels in limits.json (reset_redeemed_at + reset_cleared) and the shim lifts any park
|
|
2278
|
+
# written at or before it for a window it refilled — even a client:seven_day one, which
|
|
2279
|
+
# no telemetry may lift. A park written AFTER the reset, or for a window the reset did
|
|
2280
|
+
# not refill, stays exactly as it was.
|
|
2281
|
+
lr_doc() { # lr_doc <redeemed_at epoch> <cleared csv> -> a post-reset limits.json (stale)
|
|
2282
|
+
printf '{"fetched_at":0,"source":"oauth","reset_redeemed_at":%s,"reset_cleared":"%s","buckets":[]}' "$1" "$2"
|
|
2283
|
+
}
|
|
2284
|
+
# The ND pool is shared with the blocks below: save what this block overwrites.
|
|
2285
|
+
for lr_f in acct-01/limits.json acct-02/limits.json acct-01/.limited acct-01/.client-limit-cleared; do
|
|
2286
|
+
rm -f "$WORK/lr-save.${lr_f//\//_}"
|
|
2287
|
+
[ -f "$ND/$lr_f" ] && cp -p "$ND/$lr_f" "$WORK/lr-save.${lr_f//\//_}"
|
|
2288
|
+
done
|
|
2289
|
+
lr_now="$(date +%s)"
|
|
2290
|
+
lr_at=$((lr_now - 600))
|
|
2291
|
+
lr_before="$(date -u -r $((lr_at - 3600)) +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d "@$((lr_at - 3600))" +%Y-%m-%dT%H:%M:%SZ)"
|
|
2292
|
+
lr_after="$(date -u -r $((lr_at + 60)) +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d "@$((lr_at + 60))" +%Y-%m-%dT%H:%M:%SZ)"
|
|
2293
|
+
lr_doc "$lr_at" "five_hour,seven_day,seven_day_overage_included" > "$ND/acct-01/limits.json"
|
|
2294
|
+
ndl 45 10 45 > "$ND/acct-02/limits.json"
|
|
2295
|
+
printf '%s\nbucket=client:seven_day percent=100 marked_at=%s reason=client-rate-limit\n' \
|
|
2296
|
+
"$((lr_now + 345600))" "$lr_before" > "$ND/acct-01/.limited"
|
|
2297
|
+
rm -f "$ND"/acct-0*/.client-limit-cleared "$ND/.pick-seq" "$ND"/acct-0*/.last-pick
|
|
2298
|
+
: > "$ND/selection.log"
|
|
2299
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2300
|
+
[ ! -f "$ND/acct-01/.limited" ] \
|
|
2301
|
+
&& t_ok "a client:seven_day park older than a confirmed limit reset is lifted" \
|
|
2302
|
+
|| t_fail "limit reset lift" "marker kept: $(sed -n 2p "$ND/acct-01/.limited")"
|
|
2303
|
+
grep -q "acct-01 marker lifted — written before a confirmed limit reset" "$ND/selection.log" \
|
|
2304
|
+
&& t_ok "the lift is logged" || t_fail "limit reset lift" "selection.log: $(tail -2 "$ND/selection.log" | tr '\n' ' ')"
|
|
2305
|
+
# the panel's own shapes: a stream weekly_all park, and a bucket-less panel:observed one
|
|
2306
|
+
for lr_bucket in weekly_all panel:observed weekly_scoped:Fable session; do
|
|
2307
|
+
printf '%s\nbucket=%s percent=100 marked_at=%s reason=client-rate-limit source=stream\n' \
|
|
2308
|
+
"$((lr_now + 345600))" "$lr_bucket" "$lr_before" > "$ND/acct-01/.limited"
|
|
2309
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2310
|
+
[ ! -f "$ND/acct-01/.limited" ] \
|
|
2311
|
+
&& t_ok "an older $lr_bucket park is lifted by the reset" \
|
|
2312
|
+
|| t_fail "limit reset lift" "$lr_bucket kept"
|
|
2313
|
+
done
|
|
2314
|
+
printf '%s\nbucket=client:seven_day percent=100 marked_at=%s reason=client-rate-limit\n' \
|
|
2315
|
+
"$((lr_now + 345600))" "$lr_after" > "$ND/acct-01/.limited"
|
|
2316
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2317
|
+
[ -f "$ND/acct-01/.limited" ] \
|
|
2318
|
+
&& t_ok "a park written AFTER the reset stands" || t_fail "limit reset lift" "a post-reset park was lifted"
|
|
2319
|
+
printf '%s\nbucket=client:seven_day_opus percent=100 marked_at=%s reason=client-rate-limit\n' \
|
|
2320
|
+
"$((lr_now + 345600))" "$lr_before" > "$ND/acct-01/.limited"
|
|
2321
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2322
|
+
[ -f "$ND/acct-01/.limited" ] \
|
|
2323
|
+
&& t_ok "a park for a window the reset did not refill stands" \
|
|
2324
|
+
|| t_fail "limit reset lift" "seven_day_opus was lifted by a reset that did not clear it"
|
|
2325
|
+
# an expired record (older than eight days) lifts nothing
|
|
2326
|
+
lr_doc "$((lr_now - 9 * 86400))" "five_hour,seven_day" > "$ND/acct-01/limits.json"
|
|
2327
|
+
printf '%s\nbucket=client:seven_day percent=100 marked_at=2020-01-01T00:00:00Z reason=client-rate-limit\n' \
|
|
2328
|
+
"$((lr_now + 345600))" > "$ND/acct-01/.limited"
|
|
2329
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2330
|
+
[ -f "$ND/acct-01/.limited" ] \
|
|
2331
|
+
&& t_ok "an expired reset record lifts nothing" || t_fail "limit reset lift" "an expired record lifted a park"
|
|
2332
|
+
# the report agrees with the shim: a superseded park is not `limited`
|
|
2333
|
+
lr_doc "$lr_at" "five_hour,seven_day,seven_day_overage_included" > "$ND/acct-01/limits.json"
|
|
2334
|
+
printf '%s\nbucket=weekly_all percent=95 marked_at=%s reason=client-rate-limit source=stream\n' \
|
|
2335
|
+
"$((lr_now + 345600))" "$lr_before" > "$ND/acct-01/.limited"
|
|
2336
|
+
lr_json="$(CLAUDE_ACCOUNTS_ROOT="$ND" claude-accounts list --json 2>/dev/null)"
|
|
2337
|
+
printf '%s' "$lr_json" | python3 -c '
|
|
2338
|
+
import json, sys
|
|
2339
|
+
row = next(a for a in json.load(sys.stdin)["accounts"] if a["id"] == "acct-01")
|
|
2340
|
+
assert row["limited"] is False and row["status"] != "limited", row
|
|
2341
|
+
assert row["usage"]["reset_cleared"] == ["five_hour", "seven_day", "seven_day_overage_included"], row["usage"]
|
|
2342
|
+
assert row["usage"]["reset_redeemed_at"].endswith("Z"), row["usage"]
|
|
2343
|
+
' && t_ok "list --json reports a superseded park as not limited, with the reset record" \
|
|
2344
|
+
|| t_fail "limit reset report" "$(printf '%s' "$lr_json" | head -c 400)"
|
|
2345
|
+
lr_text="$(CLAUDE_ACCOUNTS_ROOT="$ND" claude-accounts list 2>/dev/null | grep '^acct-01')"
|
|
2346
|
+
case "$lr_text" in
|
|
2347
|
+
*LIMITED*) t_fail "limit reset report" "text list still says: $lr_text" ;;
|
|
2348
|
+
*) t_ok "the text list agrees: a superseded park is not LIMITED" ;;
|
|
2349
|
+
esac
|
|
2350
|
+
lr_status="$(CLAUDE_ACCOUNTS_ROOT="$ND" CLAUDE_MULTIACC_PATH_PROBE=0 claude-accounts status 2>/dev/null)"
|
|
2351
|
+
check "status says the park was lifted by the reset" "lifted — written before a confirmed limit reset" "$lr_status"
|
|
2352
|
+
# ...and the transcript scan cannot re-create a park from a rejection the reset answered,
|
|
2353
|
+
# on a machine that never saw the source's local watermark (a peer): the record is enough.
|
|
2354
|
+
lr_iso() { date -u -r "$1" +%Y-%m-%dT%H:%M:%S.000Z 2>/dev/null || date -u -d "@$1" +%Y-%m-%dT%H:%M:%S.000Z; }
|
|
2355
|
+
rm -f "$ND"/acct-0*/.limited "$ND"/acct-0*/.client-limit-cleared "$ND"/acct-0*/.client-scan
|
|
2356
|
+
lr_doc "$((lr_now - 60))" "five_hour,seven_day,seven_day_overage_included" > "$ND/acct-01/limits.json"
|
|
2357
|
+
mkclientlimit "$ND/acct-01" "5d1c7a42-1f4e-4c55-9c3e-0d2f6a8b9e10" "$((lr_now + 3600))" "$(lr_iso $((lr_now - 300)))"
|
|
2358
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2359
|
+
[ ! -f "$ND/acct-01/.limited" ] \
|
|
2360
|
+
&& t_ok "a rejection the reset answered cannot re-park the account from the transcript" \
|
|
2361
|
+
|| t_fail "limit reset watermark" "the scan re-created: $(sed -n 2p "$ND/acct-01/.limited")"
|
|
2362
|
+
rm -f "$ND"/acct-0*/.client-scan
|
|
2363
|
+
mkclientlimit "$ND/acct-01" "5d1c7a42-1f4e-4c55-9c3e-0d2f6a8b9e10" "$((lr_now + 3600))" "$(lr_iso $((lr_now - 20)))"
|
|
2364
|
+
CLAUDE_ACCOUNTS_ROOT="$ND" claude >/dev/null 2>&1
|
|
2365
|
+
[ -f "$ND/acct-01/.limited" ] \
|
|
2366
|
+
&& t_ok "a rejection AFTER the reset still parks the account" \
|
|
2367
|
+
|| t_fail "limit reset watermark" "a post-reset rejection was ignored"
|
|
2368
|
+
rm -rf "$ND/acct-01/projects" "$ND/acct-01/.sessions-index"
|
|
2369
|
+
rm -f "$ND"/acct-0*/.limited "$ND"/acct-0*/.client-limit-cleared "$ND"/acct-0*/.client-scan
|
|
2370
|
+
for lr_f in acct-01/limits.json acct-02/limits.json acct-01/.limited acct-01/.client-limit-cleared; do
|
|
2371
|
+
rm -f "$ND/$lr_f"
|
|
2372
|
+
[ -f "$WORK/lr-save.${lr_f//\//_}" ] && cp -p "$WORK/lr-save.${lr_f//\//_}" "$ND/$lr_f"
|
|
2373
|
+
done
|
|
2374
|
+
|
|
2274
2375
|
# ---- 16-nodata-shorten. an offender pass must never SHORTEN a weekly client marker ---
|
|
2275
2376
|
# The writer's offenders branch used to overwrite `.limited` unconditionally: a session
|
|
2276
2377
|
# bucket crossing the threshold (+1h reset) replaced a client:seven_day marker four days
|
|
@@ -2539,6 +2640,76 @@ EOF
|
|
|
2539
2640
|
[ $? -eq 0 ] && t_ok "an all-zero payload still writes no session_percent (2026-09-04 holds)" \
|
|
2540
2641
|
|| t_fail "allzero still unknown" "see $ND/acct-01/limits.json"
|
|
2541
2642
|
|
|
2643
|
+
# ---- 16-idle-marker. an INFERRED session 0 may rank, but it may never unpark ---------
|
|
2644
|
+
# 2026-09-22, the second half of the same day: with idle accounts ranking again, the
|
|
2645
|
+
# operator's `claude --resume` landed on acct-14 and was rejected inside a minute.
|
|
2646
|
+
# selection.log: "acct-14 client limit cleared by newer telemetry (0%)" one second before
|
|
2647
|
+
# the pick, and "LIMITED by its own session (five_hour)" 54s after it. acct-14 held a
|
|
2648
|
+
# client:five_hour marker from a REAL 429, and its telemetry — fetched at 13:04:16Z,
|
|
2649
|
+
# WHILE the client was being rejected — still reported the session bucket `percent: 0,
|
|
2650
|
+
# resets_at: null`. So a silent session bucket does NOT prove an idle account: the
|
|
2651
|
+
# endpoint may simply never report the 5h window. Recovery has to be MEASURED. The
|
|
2652
|
+
# clearing rule read max_percent, which is the WEEKLY peak when the session bucket is
|
|
2653
|
+
# silent — the cross-signal borrow the aggregation itself refuses to make.
|
|
2654
|
+
mk_client_marker() { # mk_client_marker <acct dir> <bucket> <age seconds> <reset in>
|
|
2655
|
+
python3 - "$1" "$2" "$3" "$4" <<'PJ'
|
|
2656
|
+
import sys, time, os
|
|
2657
|
+
d, bucket, age, ahead = sys.argv[1], sys.argv[2], int(sys.argv[3]), int(sys.argv[4])
|
|
2658
|
+
now = int(time.time())
|
|
2659
|
+
marked = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(now - age))
|
|
2660
|
+
with open(os.path.join(d, '.limited'), 'w') as f:
|
|
2661
|
+
f.write(f'{now + ahead}\n'
|
|
2662
|
+
f'bucket=client:{bucket} percent=100 marked_at={marked} '
|
|
2663
|
+
f'reason=client-rate-limit\n')
|
|
2664
|
+
PJ
|
|
2665
|
+
}
|
|
2666
|
+
# the acct-14 shape: a live 5h rejection, and telemetry whose session bucket is silent
|
|
2667
|
+
mk_client_marker "$ND/acct-01" five_hour 1200 7200
|
|
2668
|
+
ndlimits "$WORK/usage-weekly-only.json" --quiet >/dev/null
|
|
2669
|
+
{ [ -f "$ND/acct-01/.limited" ] \
|
|
2670
|
+
&& 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)"; } \
|
|
2671
|
+
&& t_ok "an inferred session 0 cannot clear a client:five_hour marker (writer)" \
|
|
2672
|
+
|| t_fail "inferred unpark (writer)" "marker gone, or session_inferred not recorded"
|
|
2673
|
+
|
|
2674
|
+
# ...and the SHIM must agree, because it is the shim that logged the clear before the
|
|
2675
|
+
# bad pick. The rival is WORSE on weekly and must still take every launch.
|
|
2676
|
+
ndl 60 10 60 > "$ND/acct-02/limits.json"
|
|
2677
|
+
rm -f "$ND/.pick-seq" "$ND"/acct-0*/.last-pick "$ND/acct-02/.limited"
|
|
2678
|
+
: > "$ND/selection.log"
|
|
2679
|
+
nd_park_hits=0
|
|
2680
|
+
for _ in $(seq 1 6); do
|
|
2681
|
+
case "$(CLAUDE_ACCOUNTS_ROOT="$ND" claude 2>&1)" in *CFG=acct-01*) nd_park_hits=$((nd_park_hits+1)) ;; esac
|
|
2682
|
+
done
|
|
2683
|
+
{ [ "$nd_park_hits" = "0" ] && [ -f "$ND/acct-01/.limited" ] \
|
|
2684
|
+
&& ! grep -q "cleared by newer telemetry" "$ND/selection.log"; } \
|
|
2685
|
+
&& t_ok "the shim never unparks a 5h marker on an inferred reading (0 of 6 picks)" \
|
|
2686
|
+
|| t_fail "inferred unpark (shim)" \
|
|
2687
|
+
"the parked account took $nd_park_hits of 6; $(tail -1 "$ND/selection.log" 2>/dev/null)"
|
|
2688
|
+
|
|
2689
|
+
# The #22 behaviour that must SURVIVE: a MEASURED low session reading still clears a 5h
|
|
2690
|
+
# marker. That window self-heals in hours, which is why #22 (2026-09-03) had to clear it
|
|
2691
|
+
# — this fix narrows the evidence, it does not park an account until its reset.
|
|
2692
|
+
mk_client_marker "$ND/acct-01" five_hour 1200 7200
|
|
2693
|
+
cat > "$WORK/usage-measured-session.json" <<'EOF'
|
|
2694
|
+
{"limits":[
|
|
2695
|
+
{"kind":"session","group":"session","percent":5,"resets_at":"2099-01-01T00:00:00+00:00","scope":null},
|
|
2696
|
+
{"kind":"weekly_all","group":"weekly","percent":37,"resets_at":"2099-01-05T00:00:00+00:00","scope":null}
|
|
2697
|
+
]}
|
|
2698
|
+
EOF
|
|
2699
|
+
ndlimits "$WORK/usage-measured-session.json" --quiet >/dev/null
|
|
2700
|
+
{ [ ! -f "$ND/acct-01/.limited" ] \
|
|
2701
|
+
&& 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)"; } \
|
|
2702
|
+
&& t_ok "a MEASURED low session reading still clears a five_hour marker (#22 holds)" \
|
|
2703
|
+
|| t_fail "measured recovery" "see $ND/acct-01/.limited"
|
|
2704
|
+
|
|
2705
|
+
# A WEEKLY marker is untouched by any of this — it was already un-clearable.
|
|
2706
|
+
mk_client_marker "$ND/acct-01" seven_day 1200 7200
|
|
2707
|
+
ndlimits "$WORK/usage-measured-session.json" --quiet >/dev/null
|
|
2708
|
+
[ -f "$ND/acct-01/.limited" ] \
|
|
2709
|
+
&& t_ok "a weekly marker still outlives even a measured reading (2026-09-04 holds)" \
|
|
2710
|
+
|| t_fail "weekly marker cleared" "see $ND/acct-01"
|
|
2711
|
+
rm -f "$ND"/acct-0*/.limited "$ND"/acct-0*/.client-limit-cleared
|
|
2712
|
+
|
|
2542
2713
|
# ---- 16-nodata-blind. a fresh timestamp is not a usable reading ---------------------
|
|
2543
2714
|
# The third face of the same defect: blindness was judged on fetched_at alone, so a pool
|
|
2544
2715
|
# of freshly-written no_data documents looked FRESH — selection.log carried no
|
|
@@ -5990,6 +6161,34 @@ for k in ('max_percent', 'weekly_percent', 'session_percent'):
|
|
|
5990
6161
|
EOF
|
|
5991
6162
|
[ $? -eq 0 ] && t_ok "codex: an all-zero payload still writes no session_percent (2026-09-04 holds)" \
|
|
5992
6163
|
|| t_fail "codex allzero still unknown" "see $CD/acct-01/limits.json"
|
|
6164
|
+
|
|
6165
|
+
# ...and the same guard one window down: an INFERRED session 0 may rank, never unpark.
|
|
6166
|
+
# codex reports no window at all for a closed one, so silence is its NORMAL shape and
|
|
6167
|
+
# says nothing about a 429 the client actually received. codex's only telemetry-driven
|
|
6168
|
+
# clear lives here in `limits` (the shim has none — marker_active waits for the reset),
|
|
6169
|
+
# so this is the single place the rule has to hold.
|
|
6170
|
+
mk_client_marker "$CD/acct-01" 5h 1200 7200
|
|
6171
|
+
cdlimits "$WORK/cx-usage-weekly-only.json" --quiet >/dev/null
|
|
6172
|
+
{ [ -f "$CD/acct-01/.limited" ] \
|
|
6173
|
+
&& 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)"; } \
|
|
6174
|
+
&& t_ok "codex: an inferred session 0 cannot clear a client:5h marker" \
|
|
6175
|
+
|| t_fail "codex inferred unpark" "marker gone, or session_inferred not recorded"
|
|
6176
|
+
|
|
6177
|
+
# ...while a MEASURED short-window reading still clears it (#22 holds on codex too).
|
|
6178
|
+
mk_client_marker "$CD/acct-01" 5h 1200 7200
|
|
6179
|
+
cat > "$WORK/cx-usage-measured-session.json" <<EOF
|
|
6180
|
+
{"email":"nd@cx","plan_type":"pro",
|
|
6181
|
+
"rate_limit":{"allowed":true,"limit_reached":false,
|
|
6182
|
+
"primary_window":{"used_percent":5,"limit_window_seconds":18000,"reset_at":$((now+9000))},
|
|
6183
|
+
"secondary_window":{"used_percent":37,"limit_window_seconds":604800,"reset_at":$((now+90000))}},
|
|
6184
|
+
"additional_rate_limits":[]}
|
|
6185
|
+
EOF
|
|
6186
|
+
cdlimits "$WORK/cx-usage-measured-session.json" --quiet >/dev/null
|
|
6187
|
+
{ [ ! -f "$CD/acct-01/.limited" ] \
|
|
6188
|
+
&& 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)"; } \
|
|
6189
|
+
&& t_ok "codex: a MEASURED low session reading still clears a 5h marker (#22 holds)" \
|
|
6190
|
+
|| t_fail "codex measured recovery" "see $CD/acct-01/.limited"
|
|
6191
|
+
rm -f "$CD"/acct-0*/.limited "$CD"/acct-0*/.client-limit-cleared
|
|
5993
6192
|
rm -f "$CD"/acct-0*/.limited
|
|
5994
6193
|
|
|
5995
6194
|
# ---- C14. oauth refresh via the token endpoint --------------------------------------
|
|
@@ -7102,6 +7301,13 @@ if CODEX_MULTIACC_AUTO_RESET=1 python3 -m unittest discover -s "$REPO_DIR/tests"
|
|
|
7102
7301
|
else
|
|
7103
7302
|
t_fail "codex automatic reset suite" "see unittest output above"
|
|
7104
7303
|
fi
|
|
7304
|
+
# ...and its claude twin: the cedar_ember status rides on the usage response, the claim
|
|
7305
|
+
# is idempotent across the fleet, and a confirmed reset lifts even a weekly client park.
|
|
7306
|
+
if CLAUDE_MULTIACC_AUTO_RESET=1 python3 -m unittest discover -s "$REPO_DIR/tests" -p 'test_claude_reset*.py'; then
|
|
7307
|
+
t_ok "claude: automatic limit-reset integration suite"
|
|
7308
|
+
else
|
|
7309
|
+
t_fail "claude automatic reset suite" "see unittest output above"
|
|
7310
|
+
fi
|
|
7105
7311
|
|
|
7106
7312
|
# ---- summary ---------------------------------------------------------------------
|
|
7107
7313
|
echo
|