headroom/tests/test_cli
Abhay Singh d7b25ae3bb
fix(wrap/serena): install Serena from the serena-agent PyPI wheel, not the git source
## Description

`headroom/mcp_registry/install.py` (`build_serena_spec`) and the
wrap-time Serena pre-index in `headroom/cli/wrap.py` both ran:

```
uvx --from git+https://github.com/oraios/serena serena ...
```

The git source forces a from-source build. On proot-based filesystems
(Termux + proot-distro on Android, some restricted Linux) `uv` cannot
hardlink build dependencies into a fresh build venv, so the build fails
immediately and Serena's MCP server fails to start on every `headroom
wrap codex` launch:

```
× Failed to download and build `serena-agent @ git+https://github.com/oraios/serena@<commit>`
╰─▶ failed to hardlink file ... Operation not permitted (os error 1)
```

Setting `UV_LINK_MODE=copy` fixes it in an interactive shell, but Codex
strips most env vars from the MCP subprocesses it spawns, so that
workaround does not reliably reach Serena's launch.

Serena publishes the official `serena-agent` package to PyPI with
prebuilt wheels, and it exposes the same `serena` console script
(`serena = "serena.cli:top_level"` in the project's `pyproject.toml`),
so `uvx --from serena-agent serena ...` runs the identical command
without a build step. On platforms where the git build already worked
there is no functional difference.

Fixes #2871

## 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)

## Changes Made

- `headroom/mcp_registry/install.py` (`build_serena_spec`): `--from
git+https://github.com/oraios/serena` -> `--from serena-agent`.
- `headroom/cli/wrap.py` (Serena `project index` pre-warm): same swap.
- `tests/test_mcp_registry/test_install.py`: updated the spec assertion
and added `test_build_serena_spec_uses_pypi_not_git_source` (asserts
`serena-agent` is used and no `git+` source remains).
- `tests/test_cli/test_wrap_serena_boost.py`: the pre-index test now
asserts `serena-agent` is in the command and the git source is not.

## Testing

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

### Test Output

```text
# Fail-before (source swap stashed, updated tests kept):
tests/test_mcp_registry/test_install.py::test_build_serena_spec_uses_agent_context FAILED
tests/test_mcp_registry/test_install.py::test_build_serena_spec_uses_pypi_not_git_source FAILED
tests/test_cli/test_wrap_serena_boost.py::test_preindex_runs_serena_in_cwd FAILED

# Pass-after:
tests/test_mcp_registry/ tests/test_cli/test_wrap_serena_boost.py
tests/test_cli/test_serena_migrate.py tests/test_cli/test_serena_disable.py   135 passed

# uvx ruff@0.15.17 check  -> All checks passed!
# uvx mypy@1.20.2 headroom/mcp_registry/install.py -> Success: no issues found in 1 source file
```

## Real Behavior Proof

- Environment: Windows 11, Python 3.12.11, project venv, pytest 9.1.1,
ruff 0.15.17 and mypy 1.20.2 via uvx.
- Exact command / steps: confirmed `serena-agent` exists on PyPI
(v1.6.1, homepage github.com/oraios/serena) and that its
`pyproject.toml` declares `[project.scripts] serena =
"serena.cli:top_level"`, so the `serena start-mcp-server ...` invocation
is unchanged. Swapped both `--from` sources, then fail-before with `git
stash push headroom/mcp_registry/install.py headroom/cli/wrap.py` (the
two production-asserting tests fail on the old git source) and
pass-after with `git stash pop` (135 serena-suite tests pass). Verified
no `git+https://github.com/oraios/serena` references remain in
`headroom/`.
- Observed result: `build_serena_spec` and the pre-index command now
install Serena from the `serena-agent` PyPI wheel, so a proot
environment gets the prebuilt wheel instead of a from-source build that
cannot hardlink. The migration/ledger tests, which use the old git spec
as a deliberately-stale fixture, are unaffected.
- Not tested: a live `headroom wrap codex` on a real proot/Termux device
(not available here). The change is a package-source swap verified
against Serena's own published package metadata and the existing
spec/command tests.

## 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
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] 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
- [x] I did **not** edit `CHANGELOG.md`: it is generated by
release-please from my Conventional Commit PR title (a CI guard enforces
this)

## Additional Notes

The git source was unpinned (tracked the repo default branch), so
switching to `serena-agent` from PyPI does not lose a version pin; if
anything it is more reproducible. The issue reporter also noted that
`headroom wrap codex` force-rewrites the Serena block in
`~/.codex/config.toml` from this template on every launch, which is why
the fix has to live in the package source rather than a user config edit
-- this PR puts it there.
2026-08-11 09:10:32 -07: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(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: 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: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07: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: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_wrap_claude_base_url.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07: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(wrap): stop the launch cwd from shadowing the installed package in the proxy subprocess (#2843) 2026-08-08 01:31:40 -05:00
test_wrap_codex.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_wrap_copilot.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -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: 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 feat(claude): support Claude Code in VS Code (#2752) 2026-08-03 20:14:13 -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: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07: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/serena): install Serena from the serena-agent PyPI wheel, not the git source 2026-08-11 09:10:32 -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 fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00
test_wrap_vscode.py feat(copilot): proxy VS Code models transparently (#2687) 2026-08-03 04:42:48 -07:00
test_wrap_vscode_claude.py feat(claude): support Claude Code in VS Code (#2752) 2026-08-03 20:14:13 -07:00
test_wrap_zcode.py fix: remove rtk and lean-ctx CLI context tools (#2677) 2026-07-30 22:59:41 -07:00