headroom/tests/test_cli
Rod Boev 4bf7f92417
fix(claude): surface Remote Control proxy incompatibility (#1610)
## Description

Claude Code hides Remote Control when it sees a custom
`ANTHROPIC_BASE_URL`, so `headroom wrap claude` can make the menu
disappear even though normal API requests still route through Headroom.
The reported proxy logs show no Remote Control registration, session
bootstrap, or device-attestation request at all, which means the
decision happens inside Claude before Headroom can forward anything.

This change makes that client-side incompatibility explicit in
Headroom's Claude launch flow, `headroom doctor`, and troubleshooting
docs. API proxying and the existing `ENABLE_TOOL_SEARCH` compatibility
shim stay unchanged; users who need Remote Control get a direct
instruction to launch Claude without the Headroom proxy for that
session.

Closes #1601

## 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)
- [x] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Add a Claude-specific helper and warning text for the Remote Control
custom-base incompatibility.
- Surface that warning from `headroom wrap claude` when Claude is
launched through `ANTHROPIC_BASE_URL`.
- Add a separate `headroom doctor` warning for Claude Remote Control
availability, while keeping Claude API-routing status independent.
- Document the limitation and workaround next to the existing Claude
custom-endpoint troubleshooting guidance.
- Add focused regression tests for gated and non-gated Claude routing
states, plus preservation coverage for `ENABLE_TOOL_SEARCH`.

## Testing

- [x] Unit tests pass (`uv run pytest
tests/test_issue_1601_remote_control_gate.py tests/test_cli_doctor.py
tests/test_cli/test_wrap_claude_vertex_proxy_env.py -q`)
- [x] Unit tests pass (`uv run pytest
tests/test_issue_746_tool_search.py
tests/test_cli/test_init_enable_tool_search.py -q`)
- [x] Linting passes (`uv run ruff check headroom/cli/wrap.py
tests/test_cli_doctor.py
tests/test_cli/test_wrap_claude_vertex_proxy_env.py`)
- [x] Formatting passes (`uv run ruff format --check
headroom/cli/wrap.py tests/test_cli_doctor.py
tests/test_cli/test_wrap_claude_vertex_proxy_env.py`)
- [ ] Type checking passes (`uv run mypy headroom`)
- [x] New tests added for the bugfix
- [ ] Manual testing performed

### Test Output

```text
rtk uv run pytest tests/test_issue_1601_remote_control_gate.py tests/test_cli_doctor.py tests/test_cli/test_wrap_claude_vertex_proxy_env.py -q
============================= test session starts =============================
collected 62 items
62 passed, 1 warning

rtk uv run pytest tests/test_issue_746_tool_search.py tests/test_cli/test_init_enable_tool_search.py -q
============================= test session starts =============================
collected 33 items
33 passed, 1 warning

rtk uv run ruff check headroom/cli/wrap.py tests/test_cli_doctor.py tests/test_cli/test_wrap_claude_vertex_proxy_env.py
All checks passed!

rtk uv run ruff format --check headroom/cli/wrap.py tests/test_cli_doctor.py tests/test_cli/test_wrap_claude_vertex_proxy_env.py
3 files already formatted
```

## Real Behavior Proof

- Environment: Windows, Python via `uv`, focused Claude CLI and doctor
tests.
- Exact command / steps: with Claude settings or shell environment
containing `ANTHROPIC_BASE_URL=http://127.0.0.1:8787`, run the focused
helper and doctor tests, then run the existing `ENABLE_TOOL_SEARCH`
preservation tests.
- Observed result: Headroom surfaces a Claude Remote Control warning for
custom `ANTHROPIC_BASE_URL`, while Claude API routing and
`ENABLE_TOOL_SEARCH` behavior stay intact.
- Not tested: live Claude Remote Control UI automation. The issue
evidence says Claude hides the menu before any request reaches Headroom,
so this PR proves Headroom's launch, diagnostics, and docs behavior.

## Review Readiness

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

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Additional Notes

`CHANGELOG.md` stays untouched because this repo's release pipeline
generates changelog entries from conventional commits.

This is a visibility fix, not a proxy transport restore. The issue
evidence shows Claude never sends a Remote Control request while the
custom-base gate is active, so the surviving slice is launch-time
warning, doctor warning, and documentation.

PR `#1600` is adjacent and non-blocking because `#1601` reproduces from
process-env `ANTHROPIC_BASE_URL` alone.

This intentionally changes `headroom doctor` for fully routed Claude
sessions from an all-pass result to one warnings-only result, because
the proxied Claude setup is operational for API traffic but still
incompatible with Remote Control.
2026-07-01 23:19:25 -05:00
..
conftest.py feat(wrap): make tokensave the primary coding-task compressor, Serena the backup (#1230) 2026-06-25 16:55:37 -05:00
test_copilot_auth.py fix: support Copilot Business subscription auth (#641) 2026-06-12 20:46:38 -05:00
test_init_cli.py fix(codex): retag threads on init so Codex Desktop history stays visible (#961) (#1349) 2026-06-24 10:14:40 -05:00
test_init_enable_tool_search.py fix(init): set ENABLE_TOOL_SEARCH=true so Claude Code keeps deferring tools (#746) (#995) 2026-06-19 11:26:26 -05:00
test_install_cli.py fix(cli): harden all CLI surfaces + fix docs accuracy (#1491) 2026-06-27 14:48:43 -07:00
test_main_help_version.py feat(cli): add Docker-native install flow and parity docs 2026-04-10 23:27:24 -05:00
test_mcp.py fix(mcp): auto-register headroom MCP server in wrap claude/codex and init -g 2026-05-08 17:18:32 -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(serena): migrate stale Headroom-installed Serena entry on re-wrap (#1008) 2026-06-16 15:17:13 -05: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_tokensave_helpers.py feat(wrap): make tokensave the primary coding-task compressor, Serena the backup (#1230) 2026-06-25 16:55:37 -05:00
test_tokensave_setup.py feat(wrap): make tokensave the primary coding-task compressor, Serena the backup (#1230) 2026-06-25 16:55:37 -05:00
test_unwrap_claude.py fix(wrap): preserve custom Vertex base URL (#1477) 2026-06-30 14:15:57 -05:00
test_wrap_aider.py feat(proxy): per-project savings breakdown on the dashboard (claude, codex, aider, copilot, cursor) (#803) 2026-06-10 21:04:45 -05:00
test_wrap_bridge.py fix(cli): add explicit UTF-8 encoding to file I/O in wrap commands (#1126) (#1164) 2026-06-26 12:07:03 -05:00
test_wrap_claude_base_url.py fix(wrap): write env.ANTHROPIC_BASE_URL to settings.json so daemon-spawned conversations inherit proxy (#951) (#1078) 2026-06-18 11:21:04 -05:00
test_wrap_claude_vertex_proxy_env.py fix(claude): surface Remote Control proxy incompatibility (#1610) 2026-07-01 23:19:25 -05:00
test_wrap_codex.py fix(codex): avoid duplicate headroom provider config (#1431) 2026-06-30 13:42:48 -05:00
test_wrap_continue.py fix(cli): G1 remediation — non-string clobber, per-model systemMessage, openhands gate 2026-05-25 11:54:06 -07:00
test_wrap_copilot.py fix(cli): add explicit UTF-8 encoding to file I/O in wrap commands (#1126) (#1164) 2026-06-26 12:07:03 -05:00
test_wrap_goose.py refactor(cli): factor shared wrap-subcommand scaffolding 2026-05-26 11:22:50 -07:00
test_wrap_helpers.py feat(wrap): add --1m to preserve the 1M context window on wrap claude (#1158) (#1351) 2026-06-24 10:15:19 -05:00
test_wrap_hintfile_agents.py fix(cli): add explicit UTF-8 encoding to file I/O in wrap commands (#1126) (#1164) 2026-06-26 12:07:03 -05:00
test_wrap_openclaw.py fix: stabilize codex compression, stats, and proxy lifecycle 2026-05-09 13:47:53 -07:00
test_wrap_opencode.py fix(opencode): write local MCP config (#1381) 2026-06-26 12:23:54 -05:00
test_wrap_openhands.py fix(cli): G1 remediation — non-string clobber, per-model systemMessage, openhands gate 2026-05-25 11:54:06 -07:00
test_wrap_persistent.py fix: recover persistent proxy feature checks and reject non-Copilot exchange URL (#1465) 2026-06-28 15:26:38 -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_rtk_hook_patch.py fix: patch rtk hook script to use absolute path after register_claude_hooks (#571) 2026-06-26 14:09:37 -05:00
test_wrap_rtk_metrics.py fix(observability): G3 remediation — bound cardinality + wire dead metrics 2026-05-24 10:41:56 -07:00
test_wrap_vibe.py feat: Add support for Mistral Vibe CLI (#935) 2026-06-16 14:59:51 -05:00