claude-multiacc 2.0.8 → 2.0.10
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 +37 -15
- package/bin/claude-accounts +42 -7
- package/docs/ACCOUNT_OPERATIONS.md +2 -1
- package/lib/__pycache__/audit.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/audit.py +9 -2
- package/lib/common.sh +4 -2
- package/package.json +1 -1
- package/tests/run-tests.sh +49 -1
package/bin/claude
CHANGED
|
@@ -559,17 +559,26 @@ client_auth_scan() { # $1 acct dir
|
|
|
559
559
|
return 1
|
|
560
560
|
}
|
|
561
561
|
|
|
562
|
-
mark_proven_auth_dead() { # $1 acct dir, $2 fixed safe detail
|
|
563
|
-
local marked
|
|
562
|
+
mark_proven_auth_dead() { # $1 acct dir, $2 fixed safe detail, $3 optional reason slug
|
|
563
|
+
local marked slug="${3:-auth-error}"
|
|
564
564
|
marked="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
565
|
-
{ echo "$now"; echo "reason
|
|
565
|
+
{ echo "$now"; echo "reason=$slug marked_at=$marked detail=$2"; } \
|
|
566
566
|
2>/dev/null > "$1/.expired.$$" && mv -f "$1/.expired.$$" "$1/.expired" 2>/dev/null \
|
|
567
567
|
|| rm -f "$1/.expired.$$" 2>/dev/null || true
|
|
568
568
|
}
|
|
569
569
|
|
|
570
570
|
mark_client_auth_dead() { # $1 acct dir
|
|
571
|
-
|
|
572
|
-
|
|
571
|
+
local tok
|
|
572
|
+
tok="$(acct_token "$1")"
|
|
573
|
+
if [ -n "$tok" ]; then
|
|
574
|
+
rm -f "$1/.server-token-verified" 2>/dev/null || true
|
|
575
|
+
mark_proven_auth_dead "$1" "client rejected the portable OAuth token" \
|
|
576
|
+
"setup-token-invalid"
|
|
577
|
+
sel_log "$(basename "$1") parked (setup-token-invalid) — client-reported"
|
|
578
|
+
else
|
|
579
|
+
mark_proven_auth_dead "$1" "client session failed to authenticate"
|
|
580
|
+
sel_log "$(basename "$1") parked (auth-error) — client-reported"
|
|
581
|
+
fi
|
|
573
582
|
}
|
|
574
583
|
|
|
575
584
|
mark_client_limit() { # $1 acct dir, $2 reset epoch, $3 rate limit type
|
|
@@ -750,8 +759,9 @@ token_preflight() { # $1 acct dir, $2 setup-token; rc 1 only for proven invalid
|
|
|
750
759
|
if [ "$rc" -ne 0 ] \
|
|
751
760
|
&& grep -qiE "$token_auth_pat" "$tmpd/out" "$tmpd/err" 2>/dev/null; then
|
|
752
761
|
rm -f "$marker" 2>/dev/null || true
|
|
753
|
-
mark_proven_auth_dead "$d" "portable OAuth token rejected by inference preflight"
|
|
754
|
-
|
|
762
|
+
mark_proven_auth_dead "$d" "portable OAuth token rejected by inference preflight" \
|
|
763
|
+
"setup-token-invalid"
|
|
764
|
+
sel_log "$(basename "$d") parked (setup-token-invalid) — preflight returned 401"
|
|
755
765
|
rm -rf "$tmpd"
|
|
756
766
|
return 1
|
|
757
767
|
fi
|
|
@@ -1244,9 +1254,14 @@ while :; do
|
|
|
1244
1254
|
park_detail="the account's organization has disabled Claude Code subscription access"
|
|
1245
1255
|
park_soft=$((now + PARK_SOFT_ORG))
|
|
1246
1256
|
elif grep -qiE "$PARK_AUTH" "$tmpd/out" "$tmpd/err" 2>/dev/null; then
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1257
|
+
if [ -n "$tok" ]; then
|
|
1258
|
+
park_reason="setup-token-invalid"
|
|
1259
|
+
park_detail="run rejected the portable OAuth token"
|
|
1260
|
+
else
|
|
1261
|
+
park_reason="auth-error"
|
|
1262
|
+
park_detail="run failed to authenticate"
|
|
1263
|
+
park_soft=$((now + PARK_SOFT_AUTH))
|
|
1264
|
+
fi
|
|
1250
1265
|
fi
|
|
1251
1266
|
# An account that ran out of ONE model has not run out. Cooling it down would
|
|
1252
1267
|
# take a perfectly usable account out of the pool for every other model too —
|
|
@@ -1254,13 +1269,20 @@ while :; do
|
|
|
1254
1269
|
model_scoped=0
|
|
1255
1270
|
grep -qiE "$MODEL_LIMITPAT" "$tmpd/out" "$tmpd/err" 2>/dev/null && model_scoped=1
|
|
1256
1271
|
if [ -n "$park_reason" ]; then
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1272
|
+
if [ "$park_reason" = "setup-token-invalid" ]; then
|
|
1273
|
+
rm -f "$cur/.server-token-verified" 2>/dev/null || true
|
|
1274
|
+
park_line="reason=$park_reason marked_at=$(date -u +%Y-%m-%dT%H:%M:%SZ) detail=$park_detail"
|
|
1275
|
+
else
|
|
1276
|
+
park_line="reason=$park_reason soft_until=$park_soft marked_at=$(date -u +%Y-%m-%dT%H:%M:%SZ) detail=$park_detail"
|
|
1277
|
+
fi
|
|
1278
|
+
{ echo "$now"; echo "$park_line"; } 2>/dev/null > "$cur/.expired.$$" \
|
|
1261
1279
|
&& mv -f "$cur/.expired.$$" "$cur/.expired" 2>/dev/null \
|
|
1262
1280
|
|| rm -f "$cur/.expired.$$" 2>/dev/null || true
|
|
1263
|
-
|
|
1281
|
+
if [ "$park_reason" = "setup-token-invalid" ]; then
|
|
1282
|
+
sel_log "$(basename "$cur") parked ($park_reason, durable) — see: claude-accounts expired"
|
|
1283
|
+
else
|
|
1284
|
+
sel_log "$(basename "$cur") parked ($park_reason until $park_soft) — see: claude-accounts expired"
|
|
1285
|
+
fi
|
|
1264
1286
|
elif [ "$model_scoped" = 0 ]; then
|
|
1265
1287
|
{
|
|
1266
1288
|
echo $((now + 600))
|
package/bin/claude-accounts
CHANGED
|
@@ -227,6 +227,8 @@ for a in accounts:
|
|
|
227
227
|
flags = []
|
|
228
228
|
if st['state'] == 'expired':
|
|
229
229
|
flags.append('EXPIRED-LOGIN')
|
|
230
|
+
elif st['state'] == 'token-invalid':
|
|
231
|
+
flags.append('INVALID-TOKEN')
|
|
230
232
|
elif st['state'] == 'blocked':
|
|
231
233
|
flags.append('ORG-BLOCKED')
|
|
232
234
|
elif st['state'] == 'missing':
|
|
@@ -239,7 +241,7 @@ for a in accounts:
|
|
|
239
241
|
f"auth={'+'.join(auth) or 'NONE':<11} {' '.join(flags)}")
|
|
240
242
|
bad = [a for a in accounts
|
|
241
243
|
if audit_account(root, a, machine=machine)['state']
|
|
242
|
-
in ('expired', 'blocked', 'missing')]
|
|
244
|
+
in ('expired', 'token-invalid', 'blocked', 'missing')]
|
|
243
245
|
if bad:
|
|
244
246
|
print()
|
|
245
247
|
print(f"{len(bad)} account(s) are NOT usable — details: claude-accounts expired")
|
|
@@ -317,13 +319,15 @@ for a in doc.get('accounts', []):
|
|
|
317
319
|
aid = a['id']
|
|
318
320
|
d = os.path.join(root, aid)
|
|
319
321
|
st = audit_account(root, a, machine=machine)
|
|
320
|
-
if st['state'] in ('expired', 'blocked', 'missing'):
|
|
322
|
+
if st['state'] in ('expired', 'token-invalid', 'blocked', 'missing'):
|
|
321
323
|
needs_login.append(aid)
|
|
322
324
|
else:
|
|
323
325
|
selectable_ids.append(aid)
|
|
324
326
|
banner = {'ok': '', 'remote': ' [not logged in here — grant lives elsewhere]',
|
|
325
327
|
'missing': ' ** NO LOGIN — claude-accounts relogin %s **' % aid,
|
|
326
328
|
'expired': ' ** LOGIN EXPIRED — claude-accounts relogin %s **' % aid,
|
|
329
|
+
'token-invalid': (' ** SETUP-TOKEN INVALID — '
|
|
330
|
+
'claude-accounts login %s --token **' % aid),
|
|
327
331
|
'blocked': ' ** ORG BLOCKED — Claude Code disabled for this account **',
|
|
328
332
|
'locked': ' [login is in the macOS Keychain — locked for THIS session, fine from the Mac itself]',
|
|
329
333
|
}[st['state']]
|
|
@@ -1040,17 +1044,34 @@ cmd_expired() {
|
|
|
1040
1044
|
die "the manifest lists accounts but none could be audited — check $MANIFEST"
|
|
1041
1045
|
fi
|
|
1042
1046
|
bad="$(printf '%s\n' "$rows" \
|
|
1043
|
-
| awk -F'\t' '
|
|
1047
|
+
| awk -F'\t' '
|
|
1048
|
+
NF >= 4 && $1 != "" && $4 ~ /^(expired|token-invalid|blocked|missing|unverified)$/ {
|
|
1049
|
+
print $1
|
|
1050
|
+
}')"
|
|
1044
1051
|
if [ "$quiet" = "1" ]; then
|
|
1045
1052
|
[ -n "$bad" ] || return 0
|
|
1046
1053
|
printf '%s\n' "$bad"
|
|
1047
1054
|
return 1
|
|
1048
1055
|
fi
|
|
1049
1056
|
printf '%s\n' "$rows" | awk -F'\t' '
|
|
1050
|
-
BEGIN { bad = 0; ok = 0; remote = 0; relogin = 0 }
|
|
1057
|
+
BEGIN { bad = 0; ok = 0; remote = 0; relogin = 0; retoken = 0; unverified = 0 }
|
|
1051
1058
|
NF < 4 || $1 == "" { next } # never invent an account from a blank line
|
|
1052
1059
|
$4 == "ok" { ok++; next }
|
|
1053
1060
|
$4 == "remote" { remote++; rem = rem sprintf(" %-9s %-28s %s\n", $1, $2, $6); next }
|
|
1061
|
+
$4 == "unverified" {
|
|
1062
|
+
bad++
|
|
1063
|
+
unverified++
|
|
1064
|
+
printf " %-9s %-28s %-9s %s\n", $1, $2, $5, $6
|
|
1065
|
+
printf " %-9s %-28s %-9s fix: %s\n", "", "", "", $7
|
|
1066
|
+
next
|
|
1067
|
+
}
|
|
1068
|
+
$4 == "token-invalid" {
|
|
1069
|
+
bad++
|
|
1070
|
+
retoken++
|
|
1071
|
+
printf " %-9s %-28s %-13s %s\n", $1, $2, $5, $6
|
|
1072
|
+
printf " %-9s %-28s %-13s fix: %s\n", "", "", "", $7
|
|
1073
|
+
next
|
|
1074
|
+
}
|
|
1054
1075
|
{
|
|
1055
1076
|
bad++
|
|
1056
1077
|
relogin++
|
|
@@ -1064,13 +1085,21 @@ cmd_expired() {
|
|
|
1064
1085
|
printf "\nNot logged in here on purpose (another machine owns the grant):\n"
|
|
1065
1086
|
printf "%s", rem
|
|
1066
1087
|
}
|
|
1088
|
+
if (unverified > 0) {
|
|
1089
|
+
printf "\nVerify portable tokens:\n"
|
|
1090
|
+
printf " claude-accounts verify\n"
|
|
1091
|
+
}
|
|
1092
|
+
if (retoken > 0) {
|
|
1093
|
+
printf "\nReplace invalid portable setup-tokens:\n"
|
|
1094
|
+
printf " claude-accounts login acct-NN --token\n"
|
|
1095
|
+
}
|
|
1067
1096
|
if (relogin > 0) {
|
|
1068
1097
|
printf "\nRe-authenticate them:\n"
|
|
1069
1098
|
printf " claude-accounts relogin # every account that needs it\n"
|
|
1070
1099
|
printf " claude-accounts relogin acct-NN # just one\n"
|
|
1071
1100
|
}
|
|
1072
1101
|
}'
|
|
1073
|
-
# Exit 1 when something needs
|
|
1102
|
+
# Exit 1 when something needs attention, so cron/health checks can alert on it.
|
|
1074
1103
|
[ -z "$bad" ]
|
|
1075
1104
|
}
|
|
1076
1105
|
|
|
@@ -2164,8 +2193,14 @@ for acct in manifest.get('accounts', []):
|
|
|
2164
2193
|
os.remove(os.path.join(d, '.server-token-verified'))
|
|
2165
2194
|
except OSError:
|
|
2166
2195
|
pass
|
|
2167
|
-
|
|
2168
|
-
|
|
2196
|
+
if uses_token:
|
|
2197
|
+
mark_expired(d, 'setup-token-invalid',
|
|
2198
|
+
'portable setup-token failed a real inference')
|
|
2199
|
+
hint = (f' — portable setup-token is invalid, run: '
|
|
2200
|
+
f'claude-accounts login {aid} --token')
|
|
2201
|
+
else:
|
|
2202
|
+
mark_expired(d, 'auth-error', 'a real call came back not-authenticated')
|
|
2203
|
+
hint = f' — login is dead, run: claude-accounts relogin {aid}'
|
|
2169
2204
|
print(f'{aid} {acct["email"]}: FAIL rc={r.returncode} '
|
|
2170
2205
|
f'out={out[:120]!r} err={err!r}{hint}')
|
|
2171
2206
|
failures += 1
|
|
@@ -215,7 +215,8 @@ providers (`lib/report.py`), so a consumer writes one parser:
|
|
|
215
215
|
"accounts": [{
|
|
216
216
|
"id": "acct-05", "email": "…", "home": "mac", "added_at": "…Z",
|
|
217
217
|
"home_dir": "/Users/gas/.claude-accounts/acct-05", "adopted": false,
|
|
218
|
-
"status": "active", // active|limited|expired|blocked
|
|
218
|
+
"status": "active", // active|limited|expired|token-invalid|blocked
|
|
219
|
+
// missing|remote
|
|
219
220
|
"state": "ok", "label": "OK", "reason": "…", "fix": "…",
|
|
220
221
|
"selectable": true, "needs_login": false,
|
|
221
222
|
"credential_class": "portable", // portable|machine-local|none
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/audit.py
CHANGED
|
@@ -5,6 +5,7 @@ shim's selection rule (bin/claude: creds_dead / expired_marked / auth_dead).
|
|
|
5
5
|
States:
|
|
6
6
|
ok usable right now (live OAuth credential, or a portable server.token)
|
|
7
7
|
expired has auth material that CANNOT authenticate — needs `claude-accounts relogin`
|
|
8
|
+
token-invalid a portable setup-token was rejected — needs a new setup-token, not a login
|
|
8
9
|
blocked authenticates, but its organization has disabled Claude Code subscription
|
|
9
10
|
access — a re-login cannot fix it, so it needs an admin (or removal)
|
|
10
11
|
locked the OAuth login is in the macOS Keychain and THIS session cannot open it
|
|
@@ -193,8 +194,9 @@ def oauth_login(d, now):
|
|
|
193
194
|
return {'store': None, 'state': None, 'reason': '', 'doc': None}
|
|
194
195
|
|
|
195
196
|
|
|
196
|
-
LABELS = {'ok': 'OK', 'expired': 'EXPIRED', '
|
|
197
|
-
'
|
|
197
|
+
LABELS = {'ok': 'OK', 'expired': 'EXPIRED', 'token-invalid': 'TOKEN INVALID',
|
|
198
|
+
'blocked': 'BLOCKED', 'locked': 'KEYCHAIN LOCKED', 'missing': 'NO LOGIN',
|
|
199
|
+
'remote': 'ELSEWHERE', 'unverified': 'UNVERIFIED'}
|
|
198
200
|
|
|
199
201
|
# `import --home` speaks mac|server; machine_kind() speaks mac|linux. Same two boxes,
|
|
200
202
|
# two vocabularies — normalize, or an un-authenticated account on the very machine that
|
|
@@ -216,6 +218,8 @@ def _elsewhere(home, machine):
|
|
|
216
218
|
|
|
217
219
|
|
|
218
220
|
def _fix_for(state, aid):
|
|
221
|
+
if state == 'token-invalid':
|
|
222
|
+
return f'claude-accounts login {aid} --token'
|
|
219
223
|
if state == 'blocked':
|
|
220
224
|
# A fresh sign-in re-issues the grant and in practice clears this, so it is
|
|
221
225
|
# handled like any other dead login. If it comes back BLOCKED after a re-login,
|
|
@@ -266,6 +270,9 @@ def audit_account(root, acct, now=None, machine=None, require_verified_token=Fal
|
|
|
266
270
|
row['state'] = 'blocked'
|
|
267
271
|
row['reason'] = ('this account\'s organization has disabled Claude Code '
|
|
268
272
|
'subscription access')
|
|
273
|
+
elif slug == 'setup-token-invalid':
|
|
274
|
+
row['state'] = 'token-invalid'
|
|
275
|
+
row['reason'] = 'portable setup-token was rejected by a real inference'
|
|
269
276
|
else:
|
|
270
277
|
row['state'] = 'expired'
|
|
271
278
|
row['reason'] = f'marked dead by the pool ({detail or "authentication failed"})'
|
package/lib/common.sh
CHANGED
|
@@ -495,7 +495,8 @@ clear_auth_markers() { # $1 = acct dir
|
|
|
495
495
|
rm -f "$1/.expired" "$1/.oauth-refresh.json" 2>/dev/null || true
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
-
# TSV rows: id, email, home, state
|
|
498
|
+
# TSV rows: id, email, home, state
|
|
499
|
+
# (ok|expired|token-invalid|blocked|missing|remote|locked|unverified), label, reason, fix.
|
|
499
500
|
# Fails LOUD (nonzero, message on stderr): callers decide policy from these rows, and a
|
|
500
501
|
# silently empty audit reads exactly like a perfectly healthy pool.
|
|
501
502
|
account_audit() {
|
|
@@ -518,7 +519,8 @@ account_audit() {
|
|
|
518
519
|
# Ids the shim will NOT select — everything a human has to look at.
|
|
519
520
|
accounts_unusable() {
|
|
520
521
|
account_audit \
|
|
521
|
-
| awk -F'\t'
|
|
522
|
+
| awk -F'\t' \
|
|
523
|
+
'NF >= 4 && $1 != "" && $4 ~ /^(expired|token-invalid|blocked|missing)$/ { print $1 }'
|
|
522
524
|
}
|
|
523
525
|
|
|
524
526
|
# Ids a re-login should be pointed at. Org-blocked accounts are included: a fresh
|
package/package.json
CHANGED
package/tests/run-tests.sh
CHANGED
|
@@ -515,7 +515,7 @@ case "$out" in
|
|
|
515
515
|
*"API Error: 401"*) t_fail "revoked setup-token hides preflight 401" "401 reached caller" ;;
|
|
516
516
|
*) t_ok "revoked setup-token hides preflight 401" ;;
|
|
517
517
|
esac
|
|
518
|
-
grep -q 'reason=
|
|
518
|
+
grep -q 'reason=setup-token-invalid' "$ACC/acct-01/.expired" 2>/dev/null \
|
|
519
519
|
&& t_ok "revoked setup-token writes .expired" \
|
|
520
520
|
|| t_fail "revoked setup-token marker" ".expired missing"
|
|
521
521
|
if grep -q 'soft_until=' "$ACC/acct-01/.expired" 2>/dev/null; then
|
|
@@ -523,6 +523,37 @@ if grep -q 'soft_until=' "$ACC/acct-01/.expired" 2>/dev/null; then
|
|
|
523
523
|
else
|
|
524
524
|
t_ok "proven token rejection is durable"
|
|
525
525
|
fi
|
|
526
|
+
out="$(claude-accounts expired 2>&1)"
|
|
527
|
+
check "revoked setup-token is not mislabeled as a dead login" "TOKEN INVALID" "$out"
|
|
528
|
+
check "revoked setup-token gets the token replacement fix" \
|
|
529
|
+
"claude-accounts login acct-01 --token" "$out"
|
|
530
|
+
case "$out" in
|
|
531
|
+
*"claude-accounts relogin acct-01"*)
|
|
532
|
+
t_fail "revoked setup-token does not recommend re-login" "wrong recovery shown" ;;
|
|
533
|
+
*) t_ok "revoked setup-token does not recommend re-login" ;;
|
|
534
|
+
esac
|
|
535
|
+
# A token can be revoked after it passed preflight. The captured -p path must retire
|
|
536
|
+
# that exact verified token too, rather than giving it a one-hour generic login park.
|
|
537
|
+
rm -f "$ACC/acct-01/.expired"
|
|
538
|
+
token_digest="$(printf '%s' 'sk-ant-oat01-REVOKEDREVOKEDREVOKEDREVOKEDREVOKEDREVOKED' \
|
|
539
|
+
| { shasum -a 256 2>/dev/null || sha256sum; } | cut -d ' ' -f1)"
|
|
540
|
+
printf '%s\n' "$token_digest" > "$ACC/acct-01/.server-token-verified"
|
|
541
|
+
out="$(claude -p 'Reply OK' < /dev/null 2>&1)"
|
|
542
|
+
case "$out" in
|
|
543
|
+
*"API Error: 401"*) t_fail "revoked verified token hides runtime 401" "401 reached caller" ;;
|
|
544
|
+
*) t_ok "revoked verified token hides runtime 401" ;;
|
|
545
|
+
esac
|
|
546
|
+
grep -q 'reason=setup-token-invalid' "$ACC/acct-01/.expired" 2>/dev/null \
|
|
547
|
+
&& t_ok "revoked verified token keeps token classification" \
|
|
548
|
+
|| t_fail "revoked verified token marker" ".expired missing"
|
|
549
|
+
[ ! -f "$ACC/acct-01/.server-token-verified" ] \
|
|
550
|
+
&& t_ok "revoked verified token clears its proof" \
|
|
551
|
+
|| t_fail "revoked verified token proof" "verification marker survived"
|
|
552
|
+
if grep -q 'soft_until=' "$ACC/acct-01/.expired" 2>/dev/null; then
|
|
553
|
+
t_fail "revoked verified token is durably quarantined" "marker has a soft expiry"
|
|
554
|
+
else
|
|
555
|
+
t_ok "revoked verified token is durably quarantined"
|
|
556
|
+
fi
|
|
526
557
|
out="$(CLAUDE_ACCOUNT=acct-01 claude --resume d6ccbac0-6643-4780-a99e-3afa1683478e 2>&1)"
|
|
527
558
|
rc=$?
|
|
528
559
|
check "pinned revoked setup-token is classified before --resume" \
|
|
@@ -2463,6 +2494,11 @@ out="$(claude-accounts expired 2>&1)"
|
|
|
2463
2494
|
check "expired does not call an unverified setup-token healthy" "acct-01" "$out"
|
|
2464
2495
|
check "expired labels an unverified setup-token" "UNVERIFIED" "$out"
|
|
2465
2496
|
check "expired gives the setup-token verification fix" "claude-accounts verify" "$out"
|
|
2497
|
+
case "$out" in
|
|
2498
|
+
*"Re-authenticate them"*)
|
|
2499
|
+
t_fail "unverified setup-token does not demand re-login" "expired gave the wrong fix" ;;
|
|
2500
|
+
*) t_ok "unverified setup-token does not demand re-login" ;;
|
|
2501
|
+
esac
|
|
2466
2502
|
out="$(claude-accounts verify 2>&1)"
|
|
2467
2503
|
case "$out" in *"acct-01 a@test: PASS"*) t_ok "verify uses the portable token when the credential is dead" ;;
|
|
2468
2504
|
*) t_fail "verify token fallback" "expected acct-01 PASS, got: $(printf '%s' "$out" | grep acct-01)" ;; esac
|
|
@@ -2475,7 +2511,19 @@ state="$(python3 "$REPO_DIR/lib/audit.py" "$ACC" "$(uname -s | tr '[:upper:]' '[
|
|
|
2475
2511
|
strict-tokens | awk -F'\t' '$1=="acct-01"{print $4}')"
|
|
2476
2512
|
[ "$state" = "ok" ] && t_ok "verified setup-token is no longer false-unknown" \
|
|
2477
2513
|
|| t_fail "verified setup-token audit" "expected ok, got: $state"
|
|
2514
|
+
printf 'sk-ant-oat01-REVOKEDREVOKEDREVOKEDREVOKEDREVOKEDREVOKED' \
|
|
2515
|
+
> "$ACC/acct-01/server.token"
|
|
2516
|
+
rm -f "$ACC/acct-01/.server-token-verified"
|
|
2517
|
+
out="$(claude-accounts verify 2>&1)"
|
|
2518
|
+
check "verify calls a rejected setup-token what it is" "portable setup-token is invalid" "$out"
|
|
2519
|
+
check "verify recommends replacing a rejected setup-token" \
|
|
2520
|
+
"claude-accounts login acct-01 --token" "$out"
|
|
2521
|
+
out="$(claude-accounts expired 2>&1)"
|
|
2522
|
+
check "verify marker retains setup-token classification" "TOKEN INVALID" "$out"
|
|
2523
|
+
check "verify marker retains setup-token recovery" \
|
|
2524
|
+
"claude-accounts login acct-01 --token" "$out"
|
|
2478
2525
|
rm -f "$ACC/acct-01/server.token"
|
|
2526
|
+
rm -f "$ACC/acct-01/.expired" "$ACC/acct-01/.server-token-verified"
|
|
2479
2527
|
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-test01","refreshToken":"r","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999}}' > "$ACC/acct-01/.credentials.json"
|
|
2480
2528
|
|
|
2481
2529
|
# ---- 17c. audit: machine vocabulary (home=server vs machine_kind=linux) ---------------
|