Repository navigation
feat(claude): run Claude with an Anthropic API key - #2553
Conversation
4ae734e to
0c71552
Compare
Claude setup now asks for an Anthropic API key in place of a Claude account sign-in. Buzz saves the key beside its copy of Claude through claude.sh (stdin, 0600) and gives it to every claude process as ANTHROPIC_API_KEY, protected runs and security reviews included. Without a key no turn starts, so nothing falls back on an account sign-in in ~/.claude. Saving a new key restarts idle sessions. Everything about how Claude authenticates now lives in src/bundled/claude-code/auth.tsx, so a build can replace that one file with another way to sign in. Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bradley Axen <baxen@squareup.com>
The key form sat inside agent creation's and Settings' own forms, so saving a key with Enter or the button also submitted them. It is no longer a form; Enter saves the key. On macOS the key file now lives in security reviews' folder, which Buzz Security hides from contained agents, not in the download folder that protected agents can read. No key is no longer cached, so a key another Buzz saves is picked up, and a stopped save still tells running sessions the key may have changed. Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bradley Axen <baxen@squareup.com>
- Creation takes Model and Thinking's place with the key form, full width, saying the key is set up once, and waits for it (CreateConfiguration). - An agent's library card no longer asks for the key: it says Claude is missing one and links to Claude's setup in Settings. Other Model rows open setup in a dialog that closes once the key is saved. - Claude's configuration dialog: a missing key's form takes it over, and saving a key, new or replacement, closes it while the key is all it edits. - A turn that fails for a missing or rejected key says so in its report, with a buzz://settings link to Claude's setup, a new presentation-only link form for Settings targets. Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Signed-off-by: Bradley Axen <baxen@squareup.com>
The key was a 0600 file beside the review folder, which any process of the user's, unprotected agents included, could read. Native code now keeps it in the OS credential store (Keychain on macOS, readable without a prompt only by the Buzz binary; Credential Manager or Secret Service elsewhere), as it does the TypeSafe key, and never sends it back to the webview. - claude_key.rs: claude_api_key_status / claude_api_key_set, with the same loose sk-ant- check claude.sh made. - plugin_host_process_spawn takes `secrets`: native code sets the key as ANTHROPIC_API_KEY after the plugin's env, only for buzz.claude-code's claude-sh and protected releases, and refuses the secret to any other. - auth.tsx asks for the secret where it used to pass the key in env, and reviews get it the same way; claude.sh drops save-key and key. Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Signed-off-by: Bradley Axen <baxen@squareup.com>
The first-time key form split its copy around the field: why and once above, where to get it below. One paragraph above now says both, and only the replace form keeps a field description. Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Signed-off-by: Bradley Axen <baxen@squareup.com>
Codex now leads the Harness list and is new agents' default, since Claude asks for an API key before its first agent. Picking Claude without a key opens Set up Claude; closing it returns the person to the harness and choices they left, and saving shows Model and Thinking with a toast that links to Claude's setup in Settings. When Claude starts selected, a row inside the Harness group opens the same dialog. Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Signed-off-by: Bradley Axen <baxen@squareup.com>
3f69002 to
7bc65b7
Compare
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 Combined review at 3f690022 (two reviewers). Not approved: the two P2s inline block approval.
Inline findings
- [P2] Editing a remote Claude agent requires a local API key (
tabs.tsx). - [P2] Native code never re-reads the key (
claude_key.rs). - [P3] Clarify that Claude's child processes can read the key from the environment.
- [P3] Clarify what the native secret gate guarantees.
Not tied to a line
- [P3] The PR description still describes a 0600
api-keyfile written byclaude.sh save-key, and tests for it. This head keeps the key in the OS credential store; please update the description. - Optional: a
setup-tokenOAuth token saved for reviews stays atclaude-code-review-home/token, and this build no longer reads it. That folder is shared across builds, and older builds still use the token, so removing it needs a migration decision. Deleting the file also does not revoke the token.
Validation
- Targeted Vitest (
auth,setup,runtime,buzz-links): 210/210 passed. - Full Vitest: 19,121 passed, 8 failed, 22 skipped, plus 1 unhandled error. The 8 failures were all outside the changed test files; whether this PR caused them is unconfirmed.
- Native code compiled and the 4 new key tests passed, but the full native test run crashed with SIGSEGV.
- Not checked: the app with a real key, and the rendered UI.
| state.status?.state === "signed-out" && state.running !== "install"; | ||
| // Only missing credentials hold creation: an agent made before Claude is | ||
| // installed waits for the download, as it did with the Model row alone. | ||
| const ready = !!state.status && !signedOut; |
There was a problem hiding this comment.
🤖 [P2] Editing a remote Claude agent requires a local API key.
CreateConfiguration gates readiness on local signedOut and never looks at the agent's execution binding. UpdateReview edits through AgentCreateDialog, so an update to a remotely executed Claude agent, even a name-only one, can't be saved while this computer has no key, though the remote side supplies its own authentication. SettingsTab already resolves useRemote(agent) first (L1107-1109). Fix: resolve remote execution before applying the local-key gate, and gate only agents that run locally.
| .map_err(|_| "Secure storage is unavailable".to_string())??; | ||
| *key = Some(read); | ||
| } | ||
| Ok(key.as_ref().and_then(|k| k.clone())) |
There was a problem hiding this comment.
🤖 [P2] Native code never re-reads the key.
key() caches the first read for the life of the process, including Some(None). ClaudeAuth.has()/check() clear only the TypeScript cache (auth.tsx: "another Buzz on this computer may save one"), so the native layer keeps returning the stale value. When another instance or development worktree sharing the credential store saves or replaces the key, this one keeps reporting it missing, or keeps using the old (possibly revoked) key, until restart. Fix: don't cache absence, and re-read the store natively on an explicit check.
| //! the Claude processes `buzz.claude-code` starts that ask for it | ||
| //! (`plugin_host_process_spawn`'s `secrets`). On macOS the item is created by | ||
| //! the Buzz binary, so only that binary reads it without a prompt; an agent's | ||
| //! shell cannot. |
There was a problem hiding this comment.
🤖 [P3] Clarify the boundary: Claude's child processes can read the key from the environment.
This comment covers direct Keychain reads, and docs/agent-protection.md already says the key is in each Claude agent's process environment. It doesn't say that Claude's Bash, hook and MCP children inherit it. In Buzz's current unprotected copy (2.1.319), Claude scrubs credentials from those children only when CLAUDE_CODE_SUBPROCESS_ENV_SCRUB is set, and that flag also strips SSH_AUTH_SOCK, GIT_* and every *TOKEN/*KEY variable, so it isn't a usable fix. The protected release pinned here (2.1.305) hasn't been checked. Suggest stating explicitly in the docs and setup copy ("gives it only to Claude") that commands an agent runs can read the key, after confirming the behavior in the pinned version.
| && matches!( | ||
| process_id, | ||
| "claude-sh" | "claude-protected-arm64" | "claude-protected-x64" | ||
| ) |
There was a problem hiding this comment.
🤖 [P3] Clarify what this gate guarantees.
claude-sh is declared as bash -c with a script the caller supplies, so releasing the secret to it is equivalent to releasing it to any frontend caller that names this plugin. A caller can spawn it with printf %s "$ANTHROPIC_API_KEY" and read the key back through stdout. The documented threat model trusts installed plugins and runtime code, so this isn't a breach of a promised boundary. But "never sent back to the webview" is a convention in auth.tsx, not something native code enforces. Either release the key only to a declared process that can run nothing but claude, or reword the guarantee.
An update to a Claude agent that runs on an execution target went through CreateConfiguration's local-key gate, so even a rename could not be saved while this computer had no key. Resolve where the agent runs first and ask for the key only for agents that run here. Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Signed-off-by: Bradley Axen <baxen@squareup.com>
…heck Native code kept its first read for the life of the process, absence included, so a key another Buzz saved or replaced was not seen until restart. Keep only a key, and re-read the store on every status check. Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Signed-off-by: Bradley Axen <baxen@squareup.com>
Claude's children inherit ANTHROPIC_API_KEY, and keeping the key out of the web view is the plugin's convention, not a native guarantee. Say both in the docs, the setup copy and claude_key.rs. Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Signed-off-by: Bradley Axen <baxen@squareup.com>
|
🤖 Thanks, Kalvin. Addressed at
On your validation: the full native suite also crashes with SIGSEGV for me, in |
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6f38a7ea8b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Builds can replace this one file with another way to sign in, so the rest | ||
| // of the plugin reaches authentication only through its exports: keep their | ||
| // names and shapes when changing it. |
There was a problem hiding this comment.
Remove the private repository reference
The commit message names squareup/buzz-releases#133, exposing an internal repository and issue number in this public repository's permanent history. Remove that reference from the commit message before landing.
AGENTS.md reference: AGENTS.md:L234-L240
Useful? React with 👍 / 👎.
| @@ -0,0 +1,207 @@ | |||
| //! The Anthropic API key Buzz's Claude runs with, in OSS builds. Native code | |||
There was a problem hiding this comment.
Add the required commit attribution trailers
Commit 75eebec9d4f8c03a64ef7077904c51fdf92dfc74 has neither a Signed-off-by nor a Co-authored-by trailer. The hosted DCO check will reject the commit, and because it is authored by an agent, repository policy also requires crediting the human co-author.
AGENTS.md reference: AGENTS.md:L268-L274
Useful? React with 👍 / 👎.
The create dialog's Set up Claude rendered only SignIn's full variant, which leaves the sign-in action to the surface around it. A build whose auth.tsx signs in interactively (Block's) opened an empty dialog. Offer Sign in there when auth.signIn is set, as Claude's Settings dialog does, and say "Claude is set up" for credentials of either kind. Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Signed-off-by: Bradley Axen <baxen@squareup.com>
Codex leads by default because Claude asks for a key first. A build that swaps in an auth.tsx without that step can export leadsHarnesses = true to keep Claude first. Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Signed-off-by: Bradley Axen <baxen@squareup.com>
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 Alignment check with the internal auth overlay at this head: the auth.tsx export surface, leadsHarnesses, and the create dialog's Sign in all line up; with the overlay applied, tsc --noEmit and vite build pass. One cleanup inline.
| <Dialog | ||
| open={asking && signedOut} | ||
| title="Set up Claude" | ||
| onOpenChange={(next) => { |
There was a problem hiding this comment.
🤖 Closing this dialog mid sign-in leaves claude auth login running.
With an interactive auth.signIn, clicking Sign in here and then closing the dialog calls onDecline but never setup.cancel(). setup.step has no timeout, so running: "sign-in" stays set and blocks Install/Sign in elsewhere until the process ends or the person presses Cancel in Settings. ClaudeSignIn's dialog cancels on close; this one should too.
Reproduced in jsdom with a pending setup double: 1 start, 1 decline, 0 cancellations, still running after close and unmount.
Fix: call setup.cancel() here (and on unmount) when state.running === "sign-in", and add a test for closing during a pending sign-in — the new test covers only immediate success. API-key builds are unaffected; the key form starts no process.
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord’s automated source review via Wes’s account.
Reviewed head 2167b4d646521ca257de914adc57bd0aba6419cf, target base 902644b25fec016b95689c6e2dc00c3853859282, feature merge-base f30cd0f52860813ff00e2894541ebbd10c77dab2. Three code findings are inline: one P2 affecting missing-key recovery, and two P3 storage-error recovery issues.
[P2] Remove the private distributor reference from the public PR description. The “One seam” bullet names a private distributor repository and its PR. This conflicts with the repository’s public-boundary rule and exposes internal coordination metadata. Keep the generic description of the replaceable authentication contract, but remove that private reference from the public description.
Coverage: native key storage and process boundaries, runtime/session replacement, setup/create/edit flows, and Settings navigation, with independent Rocket, Mantis and Groot review plus integration. The documented trusted-plugin and inherited-child-environment limitations are not reported as new vulnerabilities.
Source inspection only. No tests, builds, application execution, live credential operations or CI validation were performed; the PR’s reported checks and signed-release behavior were not independently reproduced.
| // Buzz's copy, and the protected release (`claude-protected-*`). | ||
| if (id !== "claude" && !id.startsWith("claude-protected-")) | ||
| return spawn(id, options); | ||
| if (required && !(await this.has())) throw new Error(NO_KEY); |
There was a problem hiding this comment.
🤖 [P2] Preserve this auth failure through session startup. An existing local Claude agent upgraded from account sign-in has no saved API key, so this throws NO_KEY. But AgentSessions.open() (sessions.ts:543–550) replaces every startup rejection with "Claude could not start". That generic result reaches runtime.report(), so the new explainTurn() wiring cannot recognize the missing key or publish its Settings link; every mention repeats the generic failure. Preserve the actionable auth error through the session-start result, or report it before that catch, while retaining the refusal to launch without a key. Cover the published runtime report: the new direct explainTurn() test bypasses the catch that loses the reason.
| const signedIn = await auth.check(); | ||
| if (!signedIn) return { state: "signed-out", version }; | ||
| return { state: "ready", version, ...signedIn }; | ||
| } catch { |
There was a problem hiding this comment.
🤖 [P3] Keep credential-store failures distinct from a missing key. Once native code has cached a key, the preceding --version spawn can succeed without reading secure storage. auth.check() then calls configured(), which always rereads it; a newly locked/unavailable store or denied prompt throws here. This catch turns that failure into signed-out, hiding the native recovery message and asking the person to add a key that is already saved. Return an error status with the sanitized storage message, and cover a cached key followed by a failed status read.
| ) {} | ||
| readonly auth = new ClaudeAuth(spawn), | ||
| ) { | ||
| this.spawn = spawn && auth.authorize(spawn); |
There was a problem hiding this comment.
🤖 [P3] Do not cache a storage refusal as a bad Claude release. Wrapping all setup launches also makes the updater’s version/protocol probes request the native key. With no cached key and a locked or denied credential store, checkClaude() returns an error without a version; install() proceeds to the latest release, then testRelease() fails before its child starts. The catch adds that version to refused, so later automatic updates skip the same healthy release even after storage recovers, until a manually requested install clears the set. Keep credential-store/prelaunch failures separate from release-validation failures and retry after storage is available; add a locked-store → recovery regression.
|
🤖 Design review (critical path: agent setup / onboarding). No must-fix items. Design preference (blocks merge):
Optional:
Not checked: the running app (the PR has no screenshots), dark mode, narrow windows, the Block-build sign-in path. Design preference outstanding with @klopez4212 |
|
hi, could i please ask why this change was done? It abruptly affected my agents using claude harness, and it is not straightforward to get an API key from a company subscription account. |
Since block#2553 Claude ran only with an Anthropic API key, so agents on a Claude subscription (`claude auth login`) stopped answering. Claude now runs either way, as the owner chooses in Settings → Agent setup → Harness defaults → Claude: - Saving a key uses the key; Sign in runs `claude auth login` and uses the account. Settings shows which is in use and switches between them. - Until the owner chooses, a saved key leads, and otherwise the account Claude is signed in to, so subscription-only setups work again after updating without any step. - On the account, Buzz gives Claude no key and refuses no turn for want of one. Security reviews keep using the key: their own Claude folder holds no account sign-in. - The choice lives in agent storage and is read as saved at each check and start, so another program's choice is honoured. Fixes block#2993 Signed-off-by: Ariel Barboza <ariel@arielbarboza.com>
## Why After #2553, picking Claude in Create agent without a key popped **Set up Claude** open on its own, and closing it switched the person back to the harness they left. Builderlab handles its own missing sign-in more quietly: a compact **Sign in** button beside the Harness picker (`CreateAction`), with Model and Thinking left in place but disabled. This makes Claude's key setup work the same way. ## What While Claude has no credentials on this computer, in Create agent and when editing a local Claude agent: - **Button beside Harness:** Claude now supplies `CreateAction`, a compact capsule button like Builderlab's. It reads **Add key**, or **Sign in** in a build whose `auth.tsx` signs in interactively (`auth.signIn`). Its accessible name is `auth.attentionLabel`. Clicking it opens **Set up Claude**, with the same contents as before. - **No automatic modal:** picking Claude no longer opens Set up Claude, and closing it keeps Claude selected. - **Model and Thinking stay**, disabled, instead of being replaced by a "Claude needs an Anthropic API key" row. Create agent stays disabled until Claude is set up, as before. - **Credentials arriving** close the modal, remove the button, enable the menus, and show the existing "Claude is set up" toast. - **Returning during sign-in:** passive Back/unmount leaves provider work running. Returning to creation restores the pending setup dialog with Cancel rather than starting another sign-in. - **Closing during sign-in** calls `setup.cancel()`, so `claude auth login` doesn't keep running. This was the open review comment on #2553 ([review](#2553 (review))). - **Execution targets:** `CreateAction` now receives the agent being edited, as `CreateConfiguration` does. An agent on an execution target gets no button, so it can still be edited without a local key. Builderlab's `CreateAction` ignores the new prop. - **`onDecline` removed:** the creation dialog's `onDecline` seam (with its `left` state) existed only for Claude's turn-down-returns-you behaviour. Nothing else used it. No change to `auth.tsx` or its exports, so the downstream authentication overlay applies unchanged. ## Tests - `tabs.test.tsx` (75 pass). Replaced the auto-open/decline tests with: - picking Claude shows **Add key** and disabled Model/Thinking/Create; closing the modal keeps Claude; saving a key closes it, enables Model and shows the toast; - a new agent without a key reports not-ready with Model and Thinking disabled; - an interactive-sign-in build labels the button **Sign in** and finishes through the modal; - closing the modal while sign-in is pending calls `cancel` once (new); - editing an agent on an execution target shows no button; a local one does. - `vitest run src/bundled/claude-code src/bundled/agents src/bundled/builderlab src/features/agents`: 2,817 pass. The pre-push related-tests hook (37 files) passes. - `tsc --noEmit` passes, also with the downstream authentication overlay’s `auth.tsx` swapped in. - Built ad-hoc signed macOS apps from this commit, OSS and with the Block overlay. Both builds pass the auth-marker checks, and baxen has tried them. ## Review follow-up - `buzz_help` names the explicit build-dependent Add key / Sign in action beside Harness. - React regression exercises unmount/remount with a held modeled sign-in child, then Cancel and retry through the real ClaudeSetup process-step path. Failed on the prior production code (dialog not restored), passed with the fix. - At follow-up head `7f49bd53e4d3415a64794164b51319bb41c61a2c`, `pnpm typecheck` passes and the full Claude plugin directory passes serially (416 passed, 4 live tests skipped). An earlier two-worker run had three failures in the transcript/shell-release tests; both whole files subsequently passed (94 tests), followed by the full serial pass. - New follow-up does not claim native authentication or routed Back/Forward validation. Human verification of the remount/cancel path remains deferred: start Sign in from creation, navigate Back, return, confirm pending controls are restored, Cancel, and retry. ## Deferred - A full `vitest run` on the dev machine hit 5s timeouts in unrelated files (`App.profile`, `entity-navigation`, others), a different set each run, under load average ~60. Those files pass when rerun under lower load. CI is the clean run. --------- Signed-off-by: Bradley Axen <baxen@squareup.com> Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Co-authored-by: drone03 <3efda7d0089450ac8094dd25cfa734546dc0d6bad0ab87b64dacfff867bdc4d3@buzz.block.builderlab.xyz>
|
why was this merged? what was the rationale behind this decision? this literally makes the app unusable |
Since block#2553 Claude ran only with an Anthropic API key, so agents on a Claude subscription (`claude auth login`) stopped answering. Claude now runs either way, as the owner chooses in Settings → Agent setup → Harness defaults → Claude: - Saving a key uses the key; Sign in runs `claude auth login` and uses the account. Settings shows which is in use and switches between them. - Until the owner chooses, a saved key leads, and otherwise the account Claude is signed in to, so subscription-only setups work again after updating without any step. - On the account, Buzz gives Claude no key and refuses no turn for want of one. Security reviews keep using the key: their own Claude folder holds no account sign-in. - The choice lives in agent storage and is read as saved at each check and start, so another program's choice is honoured. Fixes block#2993 Signed-off-by: Ariel Barboza <ariel@arielbarboza.com>
|
Can you please explain the reason behind removing Claude subscription sign-in? Was this required by Anthropic, or was it a Buzz product decision? We’ve been building on Buzz, recommending it to clients with their Claude subscriptions. This update stopped those agents from working unless users add an API key with separate billing. We haven’t seen a clear announcement explaining the change, its rationale, or a migration path. Finding out through agents suddenly failing is frustrating and makes it difficult to support the people we’ve encouraged to use Buzz. Is subscription sign-in being permanently discontinued, or would you consider supporting both options as proposed in #2995? A clear answer would help us decide what to teach and recommend. |
|
@baxen can you explain this change per the above request? this has broken workflows for several of the guys on my team and it's not entirely clear if this was an accidental regression. |
Since block#2553 Claude ran only with an Anthropic API key, so agents on a Claude subscription (`claude auth login`) stopped answering. Claude now runs either way, as the owner chooses in Settings → Agent setup → Harness defaults → Claude: - Saving a key uses the key; Sign in runs `claude auth login` and uses the account. Settings shows which is in use and switches between them. - Until the owner chooses, a saved key leads, and otherwise the account Claude is signed in to, so subscription-only setups work again after updating without any step. - On the account, Buzz gives Claude no key and refuses no turn for want of one. Security reviews keep using the key: their own Claude folder holds no account sign-in. - The choice lives in agent storage and is read as saved at each check and start, so another program's choice is honoured. Fixes block#2993 Signed-off-by: Ariel Barboza <ariel@arielbarboza.com>
Use Claude account always ran `claude auth login`, opening the browser even when Claude Code was already signed in, as from a terminal. It now uses that sign-in, as Buzz did before block#2553, and signs in only when Claude Code isn't. Signed-off-by: Ariel Barboza <ariel@arielbarboza.com>
Why
Claude agents now run with an Anthropic API key. They no longer use a Claude account sign-in from
claude auth login. Security reviews used aclaude setup-tokensign-in; they now use the same key.What
auth.tsxsigns in interactively (ClaudeAuth.signIn), Set up Claude offers Sign in there, as Claude's Settings dialog does. Once a key is saved, the field becomes Replace API key. Saving closes the configuration dialog.buzz://settings?section=agents&provider=…link that opens Claude's configuration. Agents that run on an execution target don't need this computer's key, and they can be edited without one.auth.tsxexportsleadsHarnesses(falsehere), whichsrc/bundled/index.tsreads, so a build whose Claude needs no key can keep Claude first.src-tauri/src/claude_key.rs) keeps the key in the OS credential store, the same way the TypeSafe key is kept. That's Keychain on macOS, Credential Manager on Windows and Secret Service on Linux. The macOS item isxyz.block.buzz.desktop.claude-api-key, plus.debugin debug builds. No command returns the key. Native code keeps a key once it has read or saved it. When it has none, it reads the store again, and every status check re-reads the store, so a key another Buzz saves or replaces is picked up.claudeasks for the key throughplugin_host_process_spawn'ssecrets. Native code then setsANTHROPIC_API_KEYitself, after the plugin's environment. That covers agents, the spare, model discovery, protected runs and security reviews. The key is set only forbuzz.claude-code'sclaude-shandclaude-protected-*processes; any other process that asks is refused.claudewhen no key is saved. Without that, a turn would quietly use an account sign-in in~/.claude.src/bundled/claude-code/auth.tsx(exportsClaudeAuth,Step,SignIn,SignedIn,Missing). A build can swap in another sign-in by replacing that one file. squareup/buzz-releases#133 does this to keep the login sign-in in Block builds.What the key store does not guarantee
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB, because it also stripsSSH_AUTH_SOCK,GIT_*and every*TOKEN/*KEYvariable. The docs and setup copy say so.claude-shisbash -cwith a script the caller supplies, so plugin code can print the key back. Keeping the key out of the web view isauth.tsx's convention, not a native guarantee. Like the rest of the threat model, this trusts installed plugin code.Tests
vitest run src/bundled/claude-code src/bundled/agents src/features/agents src/features/navigation: 2,495 passed.cargo test --lib claude_key: the key-store tests pass. They cover a saved key used without another read, absence not kept, a check re-reading a key replaced or removed elsewhere, and an unreadable store treated as an error rather than a missing key. A related test checks that only Buzz's Claude processes get the key.tabs.test.tsx: picking Claude, then cancelling or saving, plus the toast and the Codex-first order. Editing an agent on an execution target doesn't need a local key, and editing a local one still does.cargo test --libcrashes with SIGSEGV in thewindow_state::tests on macOS. A cleanmaincheckout crashes the same way.Deferred
setup-tokenOAuth token saved for reviews by earlier builds stays atclaude-code-review-home/token. This build doesn't read it, and older builds still do, so removing it needs a migration decision. Deleting the file also wouldn't revoke the token.🤖 Generated with Claude Code