headroom/tests/test_cli
Eyal Mizrachi 0994ea04c8
fix(wrap): skip Serena project setup outside real project roots (#2574)
## Problem

`headroom wrap` runs two per-project Serena steps against the cwd:
`_scope_serena_languages()` (detect languages, pin them into
`.serena/project.yml`) and `_index_serena_project()` (`serena project
index`, to warm the symbol cache). Both assume the cwd *is* a project.

Launched from `$HOME` — an ordinary way to start an agent — that
assumption breaks badly:

- the language scan `os.walk`s the entire home directory: `Downloads/`,
VM images, backup trees, network mounts;
- the pre-index then runs `serena project index` over the same tree and
sits there until its full 300s timeout;
- so the agent appears to **hang for minutes on every launch**, with no
output after the Serena MCP registration line and nothing to suggest
indexing is what's blocking;
- and the scan writes `project.yml` into `~/.serena`, which is Serena's
own config directory rather than a project's `.serena/`.

A linked git worktree hits the same code from the other side: it's an
ephemeral checkout, so it pays for a full cold index at a path that soon
disappears — once per worktree, which adds up under any fan-out
workflow.

## Fix

Add `_serena_project_skip_reason(root)` and gate both steps on it:

- `root == $HOME` → `"$HOME is not a project"`
- top-level `.git` is a **file** rather than a directory → `"linked git
worktree"`
- otherwise `None`, and behavior is exactly as before

The reason is echoed under `--verbose`. Nothing else changes: Serena MCP
is still registered, instructions are still injected, and in the skipped
cases Serena still indexes lazily on demand — so no capability is lost,
only the wasted upfront scan.

## Testing

Five unit tests in `tests/test_cli/test_wrap_serena_boost.py` covering
an ordinary directory, a normal checkout (`.git` dir), `$HOME`, a linked
worktree (`.git` file), and a non-existent root. Full file: 22 passed.
`ruff format --check` and `ruff check` clean.

Verified manually on the reported case: `claude` launched from `$HOME`
now starts immediately instead of stalling on the index.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:31:00 -07:00
..
conftest.py refactor(wrap): retire tokensave; Serena is the code-memory MCP (#2499) 2026-07-22 20:59:24 -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(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(install): carry upstream-routing env overrides into supervised deployments (#2429) 2026-07-19 22:18:15 -07:00
test_main_help_version.py fix(wrap): replace stale-proxy detection with Vite-style port fallback (#1406) 2026-07-07 12:10:52 -05: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 fix(codex): preserve wrapped sessions and recover state (#2160) 2026-07-15 19:58:21 +00: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 feat(wrap): boost Serena — symbol-first guidance, wrap-time pre-index, repo-language scoping (#2425) 2026-07-19 14:44:37 -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(wrap): stop same-port persistent routing during claude unwrap (#2340) (#2350) 2026-07-17 16:08:40 -07: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(wrap): make RTK opt-in (off by default) across wrap subcommands (#2344) 2026-07-17 16:47:19 -07:00
test_wrap_claude_base_url.py fix(proxy): dedupe Codex WS request logging for accurate mixed-provider dashboards (#2189) 2026-07-15 18:18:34 +00:00
test_wrap_claude_finally_unbound.py fix(wrap/claude): bind _wrap_settings_path before the try (#2126) 2026-07-14 12:02:39 -04:00
test_wrap_claude_vertex_proxy_env.py fix(wrap): surface Claude Remote Control base-URL gate accurately (#1… (#1883) 2026-07-13 14:01:37 -04:00
test_wrap_codex.py refactor(wrap): retire tokensave; Serena is the code-memory MCP (#2499) 2026-07-22 20:59:24 -07: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(copilot): normalize subscription API routing (#2441) (#2455) 2026-07-20 17:15:57 -07: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(wrap): make RTK opt-in (off by default) across wrap subcommands (#2344) 2026-07-17 16:47:19 -07:00
test_wrap_goose.py refactor(cli): factor shared wrap-subcommand scaffolding 2026-05-26 11:22:50 -07:00
test_wrap_grok.py fix(grok): preserve business-seat auth while routing only inference (#2514) 2026-07-23 15:43:03 -07:00
test_wrap_helpers.py fix(wrap): drop -p short flag from wrap claude so claude's own -p/--print passes through (#2048) 2026-07-14 14:10:34 -04:00
test_wrap_hintfile_agents.py fix(wrap): make RTK opt-in (off by default) across wrap subcommands (#2344) 2026-07-17 16:47:19 -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(wrap): make RTK opt-in (off by default) across wrap subcommands (#2344) 2026-07-17 16:47:19 -07:00
test_wrap_openclaude.py fix(wrap): make RTK opt-in (off by default) across wrap subcommands (#2344) 2026-07-17 16:47:19 -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(copilot): normalize subscription API routing (#2441) (#2455) 2026-07-20 17:15:57 -07: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 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_rtk_metrics.py fix(observability): G3 remediation — bound cardinality + wire dead metrics 2026-05-24 10:41:56 -07:00
test_wrap_rtk_on_path.py fix(rtk): link managed rtk onto PATH instead of mutating the hook (#1698) 2026-07-07 12:23:26 -05:00
test_wrap_serena_boost.py fix(wrap): skip Serena project setup outside real project roots (#2574) 2026-07-26 10:31:00 -07: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 feat: Add support for Mistral Vibe CLI (#935) 2026-06-16 14:59:51 -05:00
test_wrap_zcode.py fix(wrap): make RTK opt-in (off by default) across wrap subcommands (#2344) 2026-07-17 16:47:19 -07:00