feat: add preserve_anthropic_api_key option for API-key auth (#333)#335
feat: add preserve_anthropic_api_key option for API-key auth (#333)#335rsolmano wants to merge 4 commits intoumputun:masterfrom
Conversation
…#333) ralphex unconditionally stripped ANTHROPIC_API_KEY from the child claude process environment. This broke users who authenticate Claude Code via ANTHROPIC_API_KEY rather than OAuth/keychain — claude started without credentials, printed "Not logged in", and ralphex bailed out matching that string against claude_error_patterns. The strip was deliberate: an OAuth-authenticated user with ANTHROPIC_API_KEY exported for the SDK would otherwise have claude silently switch to the API key and bill a different account. Removing the strip unconditionally is not acceptable, so this change makes it opt-in. Add `preserve_anthropic_api_key` config option and `--preserve-anthropic-api-key` CLI flag, default false (existing OAuth-protective behavior). When enabled, ANTHROPIC_API_KEY is passed through to the claude child. CLAUDECODE continues to be stripped unconditionally (prevents nested-session errors). Implementation: - new claudeChildEnv() helper in pkg/executor/executor.go - preserveAPIKey field on execClaudeRunner, PreserveAnthropicAPIKey on ClaudeExecutor - config field with INI parsing, merge sentinel only on Values (Config carries the resolved bool, matching the move_plan_on_completion pattern) - CLI flag + applyCLIOverrides plumbing - propagation to both task and review ClaudeExecutor instances in runner.go Tests: TestClaudeChildEnv (5 subtests), TestLoad_PreserveAnthropicAPIKey (3 subtests + invalid-value case), TestPreserveAnthropicAPIKeyFlag (3 subtests). Fixes umputun#333
umputun
left a comment
There was a problem hiding this comment.
couple things, mostly the first two matter:
-
CLI override is one-way and that's a footgun for billing.
cmd/ralphex/main.go:1272-1274doesif o.PreserveAnthropicAPIKey { cfg.PreserveAnthropicAPIKey = true }. Withpreserve_anthropic_api_key = truein global/local config, there's no per-run CLI way back to the safer strip behavior. For finalize that pattern is just an annoyance; forANTHROPIC_API_KEYit's billing. A user who turned this on once for an API-key project and later runs ralphex inside an OAuth context bills the wrong account silently. The siblings just below (Wait,SessionTimeout,IdleTimeout) all track*Setto handle explicit zero/false; this one should too: tracko.preserveAnthropicAPIKeySetand apply explicitfalseas override. -
Show the active auth mode in the startup banner when the flag is on.
displayMeta(cmd/ralphex/main.go:497) printsplan,branch,progress log. Nothing tells the user thatANTHROPIC_API_KEYis being passed through. Adding a line likeauth: API key passthrough(only whenpreserve_anthropic_api_keyis true; nothing when false to avoid clutter) gives the user a chance to spot a wrong-context run before claude bills the wrong account. This matters more than #1 because it's visible per-run. -
No test for the local-overrides-global merge path.
TestLoad_PreserveAnthropicAPIKeyonly covers single-config parsing. The whole reasonPreserveAnthropicAPIKeySetexists is the merge block inmergeExtraFrom, but nothing in this PR exercises global-true + local-false → false. If a future refactor drops the sentinel handling, no test catches it.TestValues_mergeFrom_TaskModel(values_test.go:2310+) is the pattern. -
nit: subtest names in
TestPreserveAnthropicAPIKeyFlaguse snake_case (flag_enables_when_config_disabled); rest ofmain_test.gouses space-separated lowercase phrases. -
nit:
ClaudeExecutor.PreserveAnthropicAPIKeyreads long. The type already implies Claude and the runner field ispreserveAPIKey.PreserveAPIKeywould match siblings (Model,Effort,Args).
There was a problem hiding this comment.
Pull request overview
Adds an opt-in configuration/CLI switch to preserve ANTHROPIC_API_KEY in the child claude process environment, restoring API-key-based Claude Code authentication while keeping the existing safer default (strip the key) for OAuth/keychain users.
Changes:
- Introduces
preserve_anthropic_api_keyconfig +--preserve-anthropic-api-keyCLI flag (defaultfalse). - Refactors claude child env construction into a helper and plumbs the option through runner → executor.
- Adds targeted unit tests for env filtering, config parsing, and CLI override semantics; updates docs/templates.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| README.md | Documents the new CLI flag and config option in usage/examples and option tables. |
| pkg/processor/runner.go | Propagates PreserveAnthropicAPIKey into task and review ClaudeExecutor instances. |
| pkg/executor/executor.go | Adds claudeChildEnv(..., preserve) helper and executor/runner plumbing to control env stripping. |
| pkg/executor/executor_test.go | Adds TestClaudeChildEnv coverage for preserve/default behavior and edge cases. |
| pkg/config/values.go | Adds INI parsing + merge sentinel (PreserveAnthropicAPIKeySet) on Values. |
| pkg/config/defaults/config | Adds commented template entry explaining when/why to enable preserving the API key. |
| pkg/config/config.go | Adds resolved PreserveAnthropicAPIKey field to runtime config and wires it from loaded values. |
| pkg/config/config_test.go | Adds config load tests for preserve option (valid/invalid values). |
| cmd/ralphex/main.go | Adds --preserve-anthropic-api-key flag and applies it as a CLI override. |
| cmd/ralphex/main_test.go | Adds tests verifying the CLI flag override semantics. |
| llms.txt | Updates user-facing usage notes re: preserving ANTHROPIC_API_KEY. |
| CLAUDE.md | Updates contributor documentation describing the new option and plumbing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - `session_timeout` config option: per-session timeout for claude (e.g., "30m", "1h"). Kills hanging sessions and continues to next iteration. CLI flag `--session-timeout` takes precedence. Disabled by default | ||
| - `idle_timeout` config option: kills claude sessions when no output for specified duration (e.g., "5m"). Resets on each output line, only fires when session goes silent. CLI flag `--idle-timeout` takes precedence. Disabled by default | ||
| - `move_plan_on_completion` config option: controls whether completed plans move to `docs/plans/completed/` on success. Default `true`. Disable for workflows that manage plan lifecycle externally (spec-driven tooling with separate archive steps) | ||
| - `preserve_anthropic_api_key` config option / `--preserve-anthropic-api-key` CLI flag: when true, `ANTHROPIC_API_KEY` is passed through to the child claude process. Required for users who authenticate Claude Code via API key rather than OAuth/keychain. Default `false` strips the key so a host-set value cannot silently override OAuth credentials and bill a different account. The merge sentinel `PreserveAnthropicAPIKeySet` lives only on `Values` (load-bearing for local-overrides-global merge); `Config` carries the resolved bool only. Plumbed through `Config.PreserveAnthropicAPIKey` → `runner.go:130/157` → `ClaudeExecutor.PreserveAnthropicAPIKey` → `execClaudeRunner.preserveAPIKey` → `claudeChildEnv()` in `pkg/executor/executor.go`. CLAUDECODE is always stripped regardless of this flag (prevents nested-session errors) |
- Add --no-preserve-anthropic-api-key companion flag for per-run safety override (go-flags rejects --bool=value, so a paired flag is the idiomatic way to express explicit-false override). On conflict the no-form wins on safety bias to avoid the billing footgun where a config-set true sticks across an OAuth-context run. - Surface ANTHROPIC_API_KEY passthrough in the startup banner (warn color) only when enabled, so users notice wrong-context runs before claude bills. - Add Values-layer merge tests covering global+local interactions and the *Set sentinel behavior (the local-explicit-false-overrides-global case is the safety reason the sentinel exists; nothing tested it). - Rename ClaudeExecutor.PreserveAnthropicAPIKey to PreserveAPIKey to match sibling field style (Model, Effort, Args). User-facing names (config key, CLI flag, Config field) keep "anthropic" for clarity. - Clean up subtest names to space-separated phrases. - Drop hard-coded line-number references in CLAUDE.md plumbing notes (paths only, since line numbers drift). Refs umputun#335
|
Thanks for the review. Pushed On #1 (two-way override). Quick caveat on the approach: jessevdk/go-flags rejects If you'd prefer a different mechanism (e.g., custom unmarshaler on a tristate type, or the same companion flag named differently like #2 — banner line printed only when passthrough is on, in #3 — added #5 — renamed only the #4 + Copilot nit — applied. |
umputun
left a comment
There was a problem hiding this comment.
scratch round-1 #1. The banner from #2 is enough on its own. If passthrough is on, the user sees auth: ANTHROPIC_API_KEY passthrough enabled every run and has a chance to bail before claude switches off the Claude Code subscription onto per-token API charges. A second --no- flag on top of that is more confusion than safety. Pls revert to the single --preserve-anthropic-api-key flag (your original shape) and drop --no-preserve-anthropic-api-key, the matching applyCLIOverrides branch, and the related tests. Keep the merge sentinel on Values though, the local-overrides-global INI merge still needs it.
One real thing left: plan mode hides the passthrough banner. runPlanMode at cmd/ralphex/main.go:998 builds startupInfo without PreserveAnthropicAPIKey, so ralphex --plan "..." with preserve_anthropic_api_key=true runs claude with the key passed through but never prints the auth: line. The executor itself is fine (processor.New at pkg/processor/runner.go:143 always wires PreserveAPIKey from AppConfig), so this is purely a visibility gap. One-line fix plus a test mirroring main_test.go:984-1013.
This matters more now since with #1 reverted the banner becomes the only safety surface for the wrong-context risk.
Per maintainer feedback on PR umputun#335: - Revert the paired --no-preserve-anthropic-api-key flag and its override branch in applyCLIOverrides. The startup banner is sufficient as a per-run safety surface; a second flag adds confusion without meaningful safety gain. Drops the two related subtests. - Fix plan-mode banner gap: runPlanMode built startupInfo without PreserveAnthropicAPIKey, so `ralphex --plan "..."` with passthrough enabled ran claude with the key but never printed the auth line. Plumb the field into the plan-mode call site and emit the auth conditional inside the plan-mode branch of printStartupInfo, mirroring the full-mode shape. Adds a subtest covering the plan-mode visibility. With the bug fixed, the banner is now universally visible whenever passthrough is active, which is what the maintainer's argument relies on. Refs umputun#335
|
Pushed
|
Fixes #333.
Summary
ralphex unconditionally stripped
ANTHROPIC_API_KEYfrom the childclaudeprocess environment. This broke users who authenticate Claude Code viaANTHROPIC_API_KEYrather than OAuth/keychain —claudestarted unauthenticated, printedNot logged in, and ralphex exited matching that string againstclaude_error_patterns.The strip was deliberate: an OAuth-authenticated user with
ANTHROPIC_API_KEYexported for the Anthropic SDK would otherwise haveclaudesilently switch to the API key and bill a different account. Removing the strip unconditionally is therefore not acceptable — this PR makes the strip behavior opt-out instead.New
preserve_anthropic_api_keyconfig option and--preserve-anthropic-api-keyCLI flag, defaultfalse(existing OAuth-protective behavior preserved). When enabled,ANTHROPIC_API_KEYis passed through to the child.CLAUDECODEcontinues to be stripped unconditionally (prevents nested-session errors).Changes
pkg/executor/executor.go— newclaudeChildEnv(env, preserveAPIKey)helper;preserveAPIKeyfield onexecClaudeRunner;PreserveAnthropicAPIKeyfield onClaudeExecutor.pkg/config/{config,values}.go— config field with INI parsing. The*Setmerge sentinel lives only onValues(load-bearing for local-overrides-global merge);Configcarries the resolved bool only, matching themove_plan_on_completionprecedent.pkg/config/defaults/config— commentedpreserve_anthropic_api_keytemplate entry with rationale.cmd/ralphex/main.go— new--preserve-anthropic-api-keyflag, override inapplyCLIOverrides.pkg/processor/runner.go— propagation to both task and reviewClaudeExecutorinstances.README.md,CLAUDE.md,llms.txt— docs.Tests (TDD)
All new tests written before implementation:
TestClaudeChildEnv(5 subtests) — covers default/preserve behavior, partial-key-match safety, missing-key edge case.TestLoad_PreserveAnthropicAPIKey(3 subtests) +TestLoad_PreserveAnthropicAPIKey_InvalidValue— INI parsing.TestPreserveAnthropicAPIKeyFlag(3 subtests) — CLI flag override semantics.Test plan
make test— all green, 87.0% total coveragemake lint— 0 issuesmake fmt— cleango test -raceon changed packages — clean./scripts/internal/prep-toy-test.sh) — recommended before merge by maintainer