feat(auth): opt-in FABRO_CLI_TRUST_HOST_AUTH so claude CLI can use a host-side Max subscription#259
Open
PeterBell wants to merge 2 commits into
Open
Conversation
…n for claude CLI
Today, when an Anthropic API key is registered via `fabro provider login`,
Fabro injects ANTHROPIC_API_KEY into the claude CLI subprocess environment.
The claude CLI then prefers that key over its own host-side credentials.
For operators on a Claude Max subscription who want CLI-backend runs to
consume Max quota rather than per-token API spend, this silently routes
billing through the API key.
This commit adds an opt-in env var, FABRO_CLI_TRUST_HOST_AUTH. When set to
a truthy value (any non-empty value except "0" or "false"):
- resolve::to_cli_credential, when called for (Anthropic, ApiKey, Claude),
returns CliCredential { env_vars: empty, login_command: None }.
- launch_env.rs therefore does not insert ANTHROPIC_API_KEY into the
spawned claude subprocess env.
- claude CLI falls back to its own credentials on the host (e.g. a
Max subscription OAuth session at ~/.claude/), reporting
apiKeySource: "none" in its init payload.
- A registered credential is still required to satisfy the resolver
gate; the key value itself is never propagated to the subprocess.
The flag must reach the worker subprocess that runs the credential
resolver. spawn_env.rs's WORKER_ENV_ALLOWLIST is extended to include
FABRO_CLI_TRUST_HOST_AUTH, so a server with the flag set propagates it
to workers it spawns.
Behavior unchanged when the flag is unset, when the provider is not
Anthropic, when the credential is not an ApiKey, or when the CLI is
not Claude. All other backends and providers continue to receive the
existing env injection.
Tested end-to-end against a Claude Max subscription: with the flag
set, a workflow run reports apiKeySource: "none" in the claude CLI's
init payload and the Anthropic console-side token count does not
increase between the credential-gate-only run and a real run.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Member
|
Thanks @PeterBell! I'm going to dig into this while on the flight I'm on. Longer term, I'm hoping to deprecate and eventually replace |
Author
|
Perfect - once I've spiked Codex in CLI mode, I'll run a new spike for both Claude and Codex using ACP |
Member
|
Great. Also if you run into issues, I would use Future setup improvement will be making them optional. |
Author
|
Thanks - will do. Although honestly I just rotate them to be safe! |
The original opt-in covered only (Anthropic, ApiKey, Claude). Extending to (OpenAI, ApiKey, Codex) and (OpenAI, CodexOAuth, Codex) closes the auth-clobber path the CLI-backend Codex spike hit in Phase 1: `resolve_agent_launch_env` was running `codex login --with-api-key` against the vault credential, which overwrites ~/.codex/auth.json with an OAuth access_token that lacks `api.responses.write` scope. Direct codex CLI invocations 401 after that, regardless of Fabro. With the flag set, the new arms return empty env_vars and no login_command for both OpenAI+Codex variants. Codex (and codex-acp under the ACP backend) falls back to ~/.codex/auth.json, which is the user's ChatGPT Pro session. Renamed claude_cli_trust_host_auth → cli_trust_host_auth to reflect that it now covers two (provider, agent) pairs. Env var name unchanged. Validated end-to-end on ACP backend with smoke-acp-claude.fabro and smoke-acp-codex.fabro at gathercommunity/cura-fabro. Both runs succeed, both return cura data, ~/.codex/auth.json shape (auth_mode="chatgpt") preserved post-run. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
For operators on a Claude Max subscription who want to use the
clibackend withprovider="anthropic", the current behavior silently bills the registered Anthropic API key per token rather than consuming Max quota. The flow:fabro provider login --provider anthropicand registers an API key.backend="cli"(the documented path to use the innerclaudeCLI's own auth).CliCredential { env_vars: { "ANTHROPIC_API_KEY": ... } }.launch_env.rsinjects that into the spawned subprocess. claude CLI seesANTHROPIC_API_KEYin its env and prefers it over any host-side OAuth credentials it already has.Verified on
0.232.0-nightly.0against the latest claude CLI (2.1.77):agent.cli.completedreportsapiKeySource: "ANTHROPIC_API_KEY".total_cost_usd: 0.063on a trivial single-step lookup).This is a blocker for the subscription-only deployment shape: any operator who wants Fabro orchestration with Max-covered execution cannot get there with current Fabro.
Short-term fix in this PR
Add an opt-in env var,
FABRO_CLI_TRUST_HOST_AUTH. Three files, +41 lines:lib/crates/fabro-static/src/env_vars.rs: declare the constant.lib/crates/fabro-server/src/spawn_env.rs: add it toWORKER_ENV_ALLOWLISTso it reaches the worker that runs the resolver.lib/crates/fabro-auth/src/resolve.rs: when the flag is truthy AND the credential is(Provider::Anthropic, AuthDetails::ApiKey, CliAgentKind::Claude),to_cli_credentialreturnsCliCredential { env_vars: empty, login_command: None }.Behavior unchanged for: any unset / falsy flag value, any provider other than Anthropic, any credential variant other than
ApiKey, any CLI agent other than Claude. Existing API-key users see no change.When the flag is set, claude CLI starts with no
ANTHROPIC_API_KEYin its environment and falls back to whatever credentials it finds on the host (typically a Max subscription OAuth session at~/.claude/). End-to-end verification on a Max subscription:apiKeySourcein the agent.cli init payload flips from"ANTHROPIC_API_KEY"to"none".The reasoning behind the env-var-and-allowlist approach (rather than a settings.toml field): smallest possible diff, easy to reason about, easy to revert. Operator's settings.toml stays untouched. Happy to refactor to a
[providers.anthropic] cli_skip_env_credentials = truefield insettings.tomlif you'd prefer that surface; the resolver-side logic is the same either way.A more robust solution we'd love to work on
This PR is the smallest correct change to unblock Max-subscription operators today. We'd be happy to follow up with a proper Anthropic OAuth credential path that mirrors the existing
CodexOAuthshape for OpenAI:AuthDetails::AnthropicOAuth { tokens, account_id, ... }variant alongsideAuthDetails::ApiKey.fabro provider login --provider anthropicdefaults to a browser OAuth PKCE flow (with--api-keyfallback for headless setups), tied to the operator's Max account.refresh_oauth_credentialinfrastructure that already covers Codex.to_cli_credentialfor(Anthropic, AnthropicOAuth, Claude)either (a) returns an empty env_vars with alogin_commandthat runsclaude /login(or equivalent) on host-side, or (b) injects the OAuth access_token directly. Whichever matches Anthropic's actual CLI semantics; we'd dig into claude CLI's auth contract in the implementation.The two approaches coexist cleanly. The env-var flag from this PR becomes "I'll manage claude's auth on the host myself"; the OAuth path becomes "Fabro manages it for me." Different deployment shapes call for different modes. No migration cost for operators on either path.
Would you be open to a follow-on PR for the OAuth variant? We have bandwidth to start on it today if there's interest. Happy to align on the credential-variant shape and the
provider loginUX before we write code, or to send a draft for review. If you'd prefer to leave the OAuth path on the roadmap and merge just this minimal env-var fix for now, that's a clean stopping point too.Notes
gathercommunity/fabro, a fork in the gather.community org. Filing on behalf of a small team that's building an agentic orchestration layer on Fabro for our own workflows.cargo test --release -p fabro-server --libin our environment (install::tests::write_artifact_store_metadata_creates_marker_in_overridden_storage_root,serve::tests::build_object_store_from_settings_rejects_partial_static_credentials,serve::tests::build_slatedb_store_uses_configured_local_root) reproduce on a cleanmaincheckout in the same environment, so they are pre-existing environment-dependent issues unrelated to this change. Bothspawn_envtests pass.Thanks for Fabro. The DOT-graph model + the CLI-backend escape hatch is exactly the shape we needed.