feat(settings): paste a Claude setup-token
P13 of the native-harness rollout (task "Bringing Codex or Claude Code Max plans into Luke"). It builds on P04/P04b/P04c (the claude_setup_token kind and the subscriptions list) and reuses P19's (#4243) cohort gate and Codex group as its model.
Settings › Coding agents gains a Claude Code group, drawn only for accounts in LUKE_NATIVE_AGENT_ACCOUNTS (nativeAgents). Unlike the ChatGPT sign-in, it needs no loopback, so the web panel shows it too, still only to the cohort.
What changed
claude-plan-section.tsx, one row, "Claude plan":- Not connected: "Run
claude setup-tokenin a terminal, then paste the token it prints." and Add token. Add token opens a password field (autocomplete off) with Cancel and Save, as the API key rows do. - Save sends
providerKeys.savewith{ provider: anthropic, kind: claude_setup_token, key }one time. When the service keeps the token, the field closes and its value is cleared. A refusal shows its reason under the field and leaves the paste in place. - Connected: the stored
subscriptionsentry's status ("Connected" forunverified/verified), its hint, and the date it was added, with Disconnect. Disconnect asks first, then removes the token withkind: claude_setup_token. - A token whose stored status is
invalid,quota_exceededorunreadableshows that status and offers Replace token. P15 owns the statuses themselves. - The group sits inside
ph-no-capture, like the API keys group.
- Not connected: "Run
host-operator.ts:providerKeySavedroppedkindbefore it reached the gateway, so the service would have stored a setup-token as an API key. It now forwardskind, the same wayproviderKeyRemovealready does.coding-agents-page.tsx: mounts the group for the cohort, between API keys and Codex.
Judgment calls
- The row is labelled "Claude plan" (the brief says "Subscription"). This matches P19's "ChatGPT plan" and P15's planned "Using your Claude plan", following one wording per idea.
- The token never reaches renderer state beyond the open field. Every answer carries only
hintandstatus.
Screenshots (synthetic data, rendered from the panel in a throwaway harness)
Not connected · pasting · connected:

The OpenAI row's mark shows as broken only because the harness doesn't serve the panel's marks/ assets, which the desktop build copies next to the bundle. This PR doesn't touch that mark, and the real panel draws it as on main.
Tests
packages/panel/src/settings/claude-plan-section.test.ts:- The group appears only for the cohort.
- The field is masked (
type=password, autocomplete off). - Save sends the trimmed token one time with its kind, then clears the field.
- The token appears nowhere in the DOM after save and is never passed to
console.*. - A refused save keeps the paste and shows why.
- Disconnect asks first and removes the token by its kind.
- A rejected token offers Replace token.
- Escape closes the field, but a composition's Escape (Safari's keyCode 229) is left to the input method (per review).
packages/host/src/host-operator.test.ts: a save forwardskind, and an API key save sends none. This test fails without the fix.
pnpm check:affected is green. The developer waived Preview runs for this rollout, so this PR merges on green.
Made with Luke