Scoped from the 2026-08-17/18 revoked-token incident: a day of 401s healed only by claude code rewriting the spare. Souveraine can refresh, rotate, and adopt logins but cannot birth one — so it mint its own PKCE grant, outside the phone/laptop sync, and say so in the Panel when every login is dead. Full Providers subsection is a later expansion.
5.1 KiB
TASK-78 — the claude login belongs to souveraine, not to claude
Status: scoped 2026-08-18, nothing built. Everything below read from source,
the live config, or the incident's own logs.
Repos: souveraine.
Connects to: TASK-67 (provider/cache split), saf/surface/04-agent-pane.md
(the Panel), saf/identity/02-agent-principal.md (provider health reporting),
the 2026-08-17/18 revoked-token incident.
The incident this exists to end
2026-08-17 21:51 a re-login elsewhere revoked the server's token family. From 22:00 the provider answered every turn with
401 Unauthorized ... "OAuth access token has been revoked."
for a day straight, on both logins, in different ways:
- primary (
~/.claude.anthropic/.credentials.json) — the whole family was revoked by the re-login; the file token that replaced it was alive (probed:/v1/models200,/v1/messages429) yet the server kept 401-ing on its in-memory copy, and nothing in the provider refreshes on a 401 — only on expiry; - spare (
~/.claude.b/.credentials.json) — grant dead since 2026-08-13; rotation correctly refused it (can_authenticate, commit0398f9f).
The only thing that healed it was Casey's open claude code session rewriting the spare at 10:14. Rotation, adoption, and refresh all worked; nothing in souveraine can birth a login. Refresh-on-401 is not the fix — a revoked family's refresh token dies with it. The fix is that souveraine mints its own grant, so a re-login on the phone (or anywhere) cannot revoke what it holds.
What to build
-
souveraine login— the same headless PKCE platform-code flow claude code itself uses, verified against third-party implementations of it:- authorize URL
https://claude.ai/oauth/authorizewithcode=true,client_id=9d1c250a-e61b-44d9-88ed-5944d1962f5e(PROD_CLIENT_IDis already a constant inclaude_subscription.rs),response_type=code,redirect_uri=https://platform.claude.com/oauth/code/callback, the provider'sSCOPES, S256code_challenge, andstate; - print the URL, wait for the pasted code — the platform page shows a
92-char string,
<code>#<fragment>; only the part before#is the authorization code; - exchange at
TOKEN_URL(https://platform.claude.com/v1/oauth/token, already a constant) asapplication/x-www-form-urlencoded— JSON returnsinvalid_grant— withgrant_type=authorization_code,code,redirect_uri,client_id,code_verifier,state; codes are single-use and PKCE is mandatory; - persist through the existing
write_back_credentialsCAS machinery. Theloginsubcommand slots into theCommandsenum insrc/main.rsbesideinit/server.
- authorize URL
-
A souveraine-owned credential file the sync does not touch.
.claude.anthropicand.claude.bsync between the phone and the laptop, so a login on one device revokes the other's — that is the machine behind the incident. Souveraine's grant must live outside the synced set (e.g.~/.souveraine/claude-oauth.json, 0600) and be added to the provider'scredential_filesin the live config. -
The Panel says it when no login can speak. The Panel is the primary surface; provider health already has a reporting path (
saf/identity/02-agent-principal.md, the agent-pane doc). Minimal scope: when every login fails authentication, the server emits the fact and the Panel renders it with thesouveraine logincommand. The full Providers subsection — a Panel popup directing to a settings page with a lot more options built out — is explicitly a later expansion, scoped and built as its own task.
Open questions
- Does a fresh login elsewhere revoke other active grants for the account? Today's incident says yes for the synced file's family, but that may be file-sync clobbering rather than Anthropic revoking grants. Empirical test after the build: log in on the phone, watch the laptop grant survive.
- Where the notice renders in the Panel's existing surfaces (settings page, footer chip, or a turn-failure banner).
Acceptance
souveraine logincompletes end-to-end on the laptop with a real login: URL → paste code → tokens persisted.- The souveraine-owned grant lives outside the synced files, and the provider
speaks through it with no
claudebinary involved at any step. - With every login dead, the server surfaces the
souveraine logincommand in the Panel without a restart. - Rotation still spans the souveraine-owned login and the synced ones.
- A login's next turn goes through with no manual intervention.
Notes for whoever picks this up
- The refresh exchange already exists (
refresh_tokeninclaude_subscription.rs, JSON, works); only the code exchange needs the form-encoded path. - Reuse
TOKEN_URL,PROD_CLIENT_ID,SCOPES,write_back_credentials,TokenState, the accounts list, rotation, and adoption. A login is just another credential file. - Probe history from the incident is in the 2026-08-18 session; the token
endpoint constants were verified against the running file
(
claudeAiOauthshape,expiresAtin ms).