# PRD prd-new: Claude forward_host_credentials - **Status:** Draft - **Author:** claude - **Created:** 2026-07-01 - **Issue:** #325 ## Summary Add `agent_provider.forward_host_credentials: true` support for the `claude` template, mirroring the existing Codex flow. When enabled, bot-bottle reads the host's Claude OAuth session key from `~/.claude.json` at launch, forwards it only to the egress sidecar, and injects a placeholder `CLAUDE_CODE_OAUTH_TOKEN` into the agent so Claude Code starts without ever seeing the real credential. ## Problem Running a Claude agent in a container today requires the operator to manually extract a long-lived OAuth token (`claude setup-token`), export it as `BOT_BOTTLE_CLAUDE_OAUTH_TOKEN`, and reference it explicitly in the manifest with `agent_provider.auth_token: "BOT_BOTTLE_CLAUDE_OAUTH_TOKEN"`. This is a two-step manual ceremony that is easy to skip or do incorrectly. The host already stores a valid Claude session in `~/.claude.json` after `claude login` or `claude setup-token`. Codex already automates an equivalent extraction from `~/.codex/auth.json`. There is no reason Claude bottles cannot do the same. ## Goals / Success Criteria - A Claude bottle with `forward_host_credentials: true` in the manifest uses the host's `~/.claude.json` session key at launch with no additional operator steps. - The agent container receives only `CLAUDE_CODE_OAUTH_TOKEN=egress-placeholder` — never the real token. - The real session key lives only in the egress sidecar's environment. - Missing, malformed, or expired host Claude auth fails launch with a clear operator-facing message. - Existing `auth_token` behavior is unchanged. - `forward_host_credentials: true` is rejected in the manifest when both `auth_token` and `forward_host_credentials` are set, since they serve the same purpose. ## Non-goals - Refreshing Claude OAuth tokens in the sidecar. - Writing a dummy `~/.claude.json` auth state to the agent (unlike the Codex flow, Claude Code reads its credential from `CLAUDE_CODE_OAUTH_TOKEN` in env, not from an auth file — no guest-side auth marker is needed). - Supporting `forward_host_credentials` for providers other than `codex` and `claude`. ## Design ### Manifest schema ```yaml agent_provider: template: claude forward_host_credentials: true ``` Rejects in manifest validation when: - Template is not `codex` or `claude`. - Both `auth_token` and `forward_host_credentials` are set. ### Host auth extraction (`contrib/claude/claude_auth.py`) Claude Code credential storage varies by platform: - **Linux**: `~/.claude/.credentials.json` - **macOS**: macOS Keychain, service `"Claude Code-credentials"` (the file path is tried first; Keychain is the fallback when the file is absent) `~/.claude.json` contains only UI state and profile metadata — no token. The credentials JSON schema (same whether from file or Keychain): ```json { "claudeAiOauth": { "accessToken": "sk-ant-oat01-...", "refreshToken": "sk-ant-ort01-...", "expiresAt": 1748276587173, "scopes": ["user:inference", "user:profile"] } } ``` `expiresAt` is in **milliseconds** (not seconds). At prepare/launch time, when `forward_host_credentials: true`: 1. Try `~/.claude/.credentials.json`; on macOS, if absent, run `security find-generic-password -s "Claude Code-credentials" -w` and parse its stdout as JSON. 2. Require a `claudeAiOauth` dict. 3. Require a non-empty `claudeAiOauth.accessToken` string. 4. If `claudeAiOauth.expiresAt` is present, divide by 1000 and require the result to be in the future. 5. Return only the access token to the launch path. Errors name the missing or invalid condition and point the operator at `claude login`, without printing token values. ### Egress route When `forward_host_credentials: true`: - Provision the session key in `provisioned_env` under `BOT_BOTTLE_CLAUDE_HOST_ACCESS_TOKEN` (new constant in `egress.py`). - Set up the `api.anthropic.com` egress route with `auth_scheme: Bearer` and `token_ref: BOT_BOTTLE_CLAUDE_HOST_ACCESS_TOKEN`. - Set `CLAUDE_CODE_OAUTH_TOKEN=egress-placeholder` in the agent env and add it to `hidden_env_names`. No dummy auth file and no `verify` step are needed — Claude Code reads the credential from the env var, not from a file. ### Constants - `CLAUDE_HOST_CREDENTIAL_TOKEN_REF = "BOT_BOTTLE_CLAUDE_HOST_ACCESS_TOKEN"` in `egress.py` (alongside the existing `CODEX_HOST_CREDENTIAL_TOKEN_REF`). - `CLAUDE_HOST_CREDENTIAL_HOSTS = ("api.anthropic.com",)` in `agent_provider.py` (alongside the existing `CODEX_HOST_CREDENTIAL_HOSTS`). ### Data flow ``` Host ~/.claude.json → bot-bottle launch │ ├──► egress sidecar env (real token only) │ └──► agent env: CLAUDE_CODE_OAUTH_TOKEN=egress-placeholder Agent → HTTPS to api.anthropic.com (via egress) Egress → injects Authorization: Bearer Egress → forwards to api.anthropic.com ``` ## Open questions None — the Codex precedent makes the design clear.