headroom/tests/test_cli
JD Davis 2d88e31a40
fix(claude): reject conflicting auth before proxy startup (#2993)
## Description

Fixes #1443.

Claude Code rejects an effective configuration containing both
ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN before any request reaches
Headroom. The existing wrapper started the proxy and mutated project
settings before Claude surfaced its generic Invalid API key message,
leaving users to guess which credential came from their shell, global
settings, or project settings.

Headroom does not own either credential, and both represent legitimate
but different auth/billing modes, so automatically deleting one would be
destructive. This PR detects the contradiction before any proxy/config
mutation and tells the user which source contains each key without
exposing credential values.

## Changes Made

- Add a pure Claude auth-conflict classifier with explicit
settings-layer precedence.
- Cover user settings, project .claude/settings.json, project
.claude/settings.local.json, and shell environment.
- Treat higher-precedence empty values as clearing inherited
credentials.
- Abort wrap claude before proxy registration/startup when both keys
remain effective.
- Add a headroom doctor failure with the same source-aware,
value-redacted remediation.
- Preserve both user credentials and require an explicit choice between
API-key billing and token/gateway auth.

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed

### Test Output

```text
151 Claude runtime, wrap, doctor, Remote Control, and MCP dependency-contract tests passed
ruff check and format checks passed
git diff --check passed
```

Branch contains current main, including the MCP v1 cap and the five
just-merged blocker PRs.

## Real Behavior Proof

- Environment: isolated local worktree on current `main` with Claude
wrapper and doctor fixtures.
- Exact command / steps: exercised conflicting and non-conflicting
shell, user, project, and local-project credential layers through the
focused wrap and doctor test suites.
- Observed result: conflicting effective credentials fail before proxy
startup or settings mutation, report only credential sources, and never
expose values.
- Not tested: a live Claude Code login with production credentials;
credential precedence and side-effect boundaries are covered by
fixtures.

## Runtime Rollout Safety

- Rollout-managed feature(s): Claude authentication-conflict preflight.
- Minimum rollout channel: normal patch release.
- Stable/default behavior changed: only configurations with both
effective credentials now stop early with actionable diagnostics.
- Kill switch / disable path: remove or clear either conflicting
credential in its reported source.
- Unsafe override required: none; Headroom deliberately does not choose
or delete a user credential.
- Qualification impact: Claude wrap, doctor, Remote Control, and MCP
dependency-contract tests must remain green.
- Rollback path: human revert restores the previous late Claude Code
rejection; no persisted migration is involved.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Safety

No credential value is returned by the classifier, printed by wrap, or
emitted in doctor JSON. The preflight runs before
_register_proxy_client, proxy startup, MCP registration, or settings
writes.
2026-08-13 23:01:59 -05:00
..
conftest.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_copilot_auth.py fix(auth): support GitHub Enterprise Copilot OAuth domain (#2192) 2026-07-14 16:07:17 -04:00
test_init_cli.py fix(cli/init): fail clearly on a target settings file with invalid JSON (#2227) 2026-07-15 18:15:45 +00:00
test_init_enable_tool_search.py fix(vscode): persist compatible Claude modes and route Copilot CAPI (#2986) 2026-08-13 15:06:41 -05:00
test_install_cli.py fix(install): consolidate Windows fallback and cleanup safety (#2980) 2026-08-13 15:05:45 -05:00
test_main_help_version.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_mcp.py feat(mcp): add streamable HTTP MCP transport (#1773) 2026-07-14 13:25:45 -04:00
test_mcp_status.py fix(mcp): mcp status checks ~/.claude.json, not only ~/.claude/mcp.json (#990) 2026-07-15 09:25:52 -05:00
test_recover_codex.py test(recover-codex): bind AF_UNIX socket via short relative path (#2396) 2026-08-02 13:14:31 -07:00
test_serena_disable.py fix(serena): stop the Serena dashboard popup and make --no-serena actually disable Serena (#1003) 2026-06-14 23:32:46 -07:00
test_serena_migrate.py fix(wrap/serena): stop creating serena_config.yml, unbricking Serena on fresh installs (#2676) 2026-07-30 20:55:47 -07:00
test_subprocess_utf8_encoding.py fix(windows): pin UTF-8 encoding on text-mode subprocess calls (#1311) 2026-06-23 12:52:49 -05:00
test_unwrap_claude.py fix(install): consolidate Windows fallback and cleanup safety (#2980) 2026-08-13 15:05:45 -05:00
test_wrap_aider.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_wrap_bridge.py fix(wrap): verify proxy deps before mutating Codex config (#1628) 2026-08-13 11:52:22 -05:00
test_wrap_claude.py fix(claude): stop forcing tool search on Foundry (#2477) 2026-08-12 00:15:07 -05:00
test_wrap_claude_base_url.py fix(vscode): persist compatible Claude modes and route Copilot CAPI (#2986) 2026-08-13 15:06:41 -05:00
test_wrap_claude_finally_unbound.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_wrap_claude_vertex_proxy_env.py fix(claude): reject conflicting auth before proxy startup (#2993) 2026-08-13 23:01:59 -05:00
test_wrap_codex.py fix(wrap): verify proxy deps before mutating Codex config (#1628) 2026-08-13 11:52:22 -05:00
test_wrap_copilot.py fix(wrap): honor Copilot OAuth wire-api override and model default (#2387) 2026-08-12 00:04:42 -05:00
test_wrap_dead_marker_selfheal.py fix(wrap): self-heal a stale ANTHROPIC_BASE_URL left by a dead proxy (#2223) 2026-07-15 19:58:10 +00:00
test_wrap_encoding.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_wrap_goose.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_wrap_grok.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_wrap_helpers.py fix(wrap): serialize shared proxy startup (#2946) 2026-08-12 12:50:09 -07:00
test_wrap_kimi.py feat(wrap): add headroom wrap kimi for Kimi CLI (#1426) 2026-07-15 21:40:58 +00:00
test_wrap_omp.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_wrap_openclaude.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_wrap_openclaw.py fix(wrap): use canonical headroom-openclaw npm package for wrap openclaw (#1969) (#2120) 2026-07-13 16:33:44 -04:00
test_wrap_opencode.py fix(wrap): verify proxy deps before mutating Codex config (#1628) 2026-08-13 11:52:22 -05:00
test_wrap_openhands.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_wrap_persistent.py feat(opencode): support Copilot subscription backend for headroom models (#2441) (#2445) 2026-07-20 11:02:14 -07:00
test_wrap_proxy_detach.py fix(wrap): detach the shared proxy on Windows so it survives an ungraceful agent close (#1464) 2026-06-30 13:49:28 -05:00
test_wrap_serena_boost.py fix(wrap): stop the Serena pre-index stalling the launch path for 300s (#2945) 2026-08-13 11:46:30 -05:00
test_wrap_stale_marker.py fix: detect and clear stale ANTHROPIC_BASE_URL from crashed wrap sessions (#1768) (#1837) 2026-07-06 08:35:40 -07:00
test_wrap_vibe.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_wrap_vscode.py fix(vscode): persist compatible Claude modes and route Copilot CAPI (#2986) 2026-08-13 15:06:41 -05:00
test_wrap_vscode_claude.py fix(vscode): persist compatible Claude modes and route Copilot CAPI (#2986) 2026-08-13 15:06:41 -05:00
test_wrap_zcode.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00