feat(auth): recover owner-held expired tokens - #166
Conversation
a518aec to
a861c86
Compare
|
Live validation on Claude Code 2.1.218 exposed one missing process-launch input in the sealed canary environment: omitting |
3477446 to
f7f13f3
Compare
|
Windows CI exposed one remaining portability defect in the test added by f7f13f3: |
6683e47 to
ae5dfc3
Compare
ae5dfc3 to
1b2c078
Compare
|
Rebased onto current Three notes from the rebase:
Happy to split (1) out for separate review if you'd prefer to look at the interaction with #196 on its own. |
- add an explicit JSON-only recovery command using Claude's native refresh path - verify owner identity and credential state without rotating stored backups - bound canary execution, process-tree cleanup, and PII-free outcomes Refs realiti4#165 Job-Id: 825aae3c-3d9c-49f9-b09f-030810e265f8 Keeper-Commit-Id: keeper-commit-work:4ccd082e-55ef-4a48-8101-72fcdd8d08c8
Keep USER and LOGNAME in the sealed canary environment so Claude Code can resolve the session profile's macOS Keychain credential instead of appearing logged out. Job-Id: 825aae3c-3d9c-49f9-b09f-030810e265f8 Keeper-Commit-Id: keeper-commit-work:89db0502-ac8e-4034-9739-d021ed9ef927
Keep matching session profiles authoritative while idle, prevent stale backup invalid_grant from falsely quarantining their lineage, and allow the bounded native canary to clear backup-derived quarantine after freshness proof.
Assert Windows process-group creation separately and gate POSIX-only signal tests so the recovery contract runs cleanly on every CI host.
1b2c078 to
b23a3a3
Compare
|
Hi! Friendly bump on this one — I've just rebased onto current |
Summary
cswap recover <num|email> --jsonfor ordinary owner-held expired OAuth access tokensCloses #165.
Safety boundary
list,status, watch, auto, and the TUI remain read-oriented and never invoke recovery. The command does not directly rotate an owner-held refresh token, read a slot backup as owner truth, bootstrap a profile, or fetch/write usage outside the existing collector claim protocol.Recovery is single-flight per slot and checks owner/profile/credential identity immediately before the request and again afterward. A manual
/loginafter the final preflight is the residual external race; the post-run identity check detects drift. macOS Keychain failures fail closed instead of falling through to a potentially stale plaintext seed.The canary runs with safe mode, no tools, no session persistence, one low-effort Haiku turn, sealed auth/provider environment, neutral cwd, ignored stdio, a hard deadline, and process-tree cleanup. It may be billable, so recovery is explicit and documented.
JSON contract
{"schemaVersion":1,"operation":"recover","accountNumber":2,"recoveryStatus":"recovered"}recoveryStatusis one ofrecovered,not_needed,retry_later, orhuman_required. The payload contains no email, credential, path, PID, provider output, or exception text.recoveredproves credential recovery only; callers continue to use normallist --jsonoutput as usage authority.Validation
uv run pytest tests/test_recovery.py tests/test_cli.py tests/test_session.py -q— 275 passedpython -m py_compile— passedgit diff --check— passedNo real account, credential, or Claude request is used by the tests.
Follow-up
Also fixes #187: matching idle session profiles remain authoritative after access-token expiry, and stale backup
invalid_grantcan no longer falsely quarantine their newer lineage.