feat: authenticate Claude Code with a long-lived token #2
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/claude-long-lived-token"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Reading Claude Code's
.credentials.jsonworks, but inherits that login's lifetime: the stored access token expires within hours, only the CLI refreshes it, and this extension deliberately will not — a second writer could rotate the refresh token out from under the harness. A user who has not opened Claude Code since yesterday therefore gets "Login expired" on a subscription that is perfectly fine.claude setup-tokenmints ansk-ant-oat01-OAuth token for the same account and audience as the one being scraped, so it authenticates the existing/api/oauth/usagerequest unchanged. A profile can now opt into using one, kept in the login keyring rather than in dconf, where a long-lived credential would sit in plaintext in every backup and in anydconf dumppasted into a bug report.Design
authModerides inside the existingprovider-profilesJSON, so there is no gschema change and an older version's value parses unchanged. The default staysharness, so existing installs come up exactly as before.detect()returns true unconditionally in token mode. Half the point of the feature: a token-backed profile needs no CLI installed at all. It also stops "hide missing providers" from making the profile vanish between choosing token mode and pasting a token.UsageRemedyvalues.not-signed-inandreauthenticatealready mean the right things; the dropdown has the profile in hand, so it picks the wording ("No token saved" / "Token rejected") and theclaude setup-tokencommand. A keyring that cannot be read stays an error rather than a remedy — no amount of pasting fixes it.tokenRevisioncounter is what tells the poller a snapshot was taken with a credential that has since been replaced, since the token itself is never in settings.The preferences verify a pasted token against the live endpoint before storing it, and only an outright rejection blocks the save — refusing a good token because the user was offline would be the worse failure. The value is write-only in the interface: cleared from the entry once stored, never read back into a widget, and deleted on forget, on switching away from token mode, and on removing the profile.
The extension never runs
claude setup-tokenitself — it is an interactive browser OAuth flow, and spawning a terminal from a shell extension is a review liability. The command is offered as a copy button, the same pattern the "Login expired" page already uses.Verified against the live endpoint
CLAUDE_CODE_OAUTH_TOKENremedy: not-signed-in→ "No token saved"remedy: reauthenticate→ "Token rejected"libsecret store → lookup → clear → confirm-gone was exercised directly, and every Adw/Gtk API the preferences use was runtime-checked (
PasswordEntryRowwith apply button,set_subtitle_lines,ComboRow.set_selected,clipboard.set(string)round-tripping real text).just checkclean, 81/81 tests pass,just packproduces a zip containing the new modules.Not verified
The preferences window itself never rendered. It needs
just install+just prefs, and the standalone route is closed because the prefs resource requires theShewtypelib. Widget APIs are individually runtime-checked; the assembled page is not.Note
--profilewas renamed to--profile-idin the probe tool: gjs claims--profilefor its own profiler, swallows the flag, and drops a capture file in the cwd.Reading Claude Code's own .credentials.json works, but inherits that login's lifetime: the stored access token expires within hours, only the CLI refreshes it, and this extension deliberately will not — a second writer could rotate the refresh token out from under the harness. A user who has not opened Claude Code since yesterday therefore gets "Login expired" on a subscription that is perfectly fine. `claude setup-token` mints an sk-ant-oat01- OAuth token for the same account and audience as the one being scraped, so it authenticates the existing /api/oauth/usage request unchanged. A profile can now opt into using one, kept in the login keyring rather than in dconf, where a long-lived credential would sit in plaintext in every backup and in any dconf dump pasted into a bug report. Token mode never falls back to reading the file: a silent fallback would hide a revoked token behind a stale read and leave the user unable to tell which credential produced the numbers on screen. It also reports the profile as available unconditionally, which is half the point — a token-backed profile needs no CLI installed at all. No new remedy values: 'not-signed-in' and 'reauthenticate' already mean the right things, and the dropdown has the profile in hand, so it picks the wording ("No token saved" / "Token rejected") and the command. A keyring that cannot be read stays an error rather than a remedy, since no amount of pasting fixes it. libsecret is imported dynamically, so an install without the typelib loses this one feature instead of the whole extension. The preferences verify a pasted token against the live endpoint before storing it, and only an outright rejection blocks the save — refusing a good token because the user was offline would be the worse failure. The value is write-only in the interface: cleared from the entry once stored, never read back into a widget, and deleted on forget, on switching away from token mode, and on removing the profile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>