mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
## Description Three related gaps in `headroom install apply` and its supervisor lifecycle, found operating a real persistent deployment on this fork: 1. `install apply` only exposed a fixed subset of `headroom proxy`'s flags (`--backend`, `--region`, `--mode`, `--port`, `--memory`, `--telemetry`, `--no-http2`). Deployments that need code-aware compression, tool-result interception, per-tool lossy-compression protection, or a named AWS profile for Bedrock had no native way to configure them through `install apply` — the generated `manifest.json` would have to be hand-edited after the fact, which silently reverts on the next `install apply` and isn't tracked anywhere. 2. Supervised runners (macOS launchd, Linux systemd/cron, Windows services/tasks) all start their runner scripts with a bare environment and do not inherit the interactive shell's exports. In particular, a custom `HEADROOM_WORKSPACE_DIR` never reached the supervised process, so `headroom install agent run` looked for its manifest in the wrong location and failed outright with "No deployment profile named 'default' is installed" even though `install apply` itself had succeeded moments earlier. 3. `install_supervisor`'s macOS branch does an unconditional `launchctl bootout` followed by a bare `bootstrap` with no retry, unlike `start_supervisor` (already fixed by #1290), which rides out the ~15s EIO (error 5) window launchd exhibits for several seconds after a bootout. This left `install apply`'s own reinstall path exposed to the same race #1290 fixed elsewhere — requiring the exact manual recovery (bootout + remove the plist + reapply) #1290 was meant to eliminate. Closes # ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [x] 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/cli/install.py`: `install apply` gains `--code-aware/--no-code-aware`, `--intercept-tool-results`, `--protect-tool-results <tool1,tool2>`, and `--bedrock-profile <profile>`, mirroring the equivalent flags already on `headroom proxy` (same names, same help text style). Also gains `--env KEY=VALUE` (repeatable). - `headroom/install/planner.py`: `build_manifest()` threads all five new parameters into `proxy_args`/`base_env`, following the exact pattern already used for `--region`/`--no-http2`. `--env` entries are merged into `base_env` last, so they can override auto-derived defaults. - `headroom/install/supervisors.py`: - `_render_unix_runner`/`_render_windows_runner` emit `export`/`$env:` lines for `base_env` before the `exec`, so `run-headroom.sh`/`ensure-headroom.sh` (and Windows equivalents) carry the environment forward to both the outer `install agent run` process and the proxy subprocess it spawns. The Docker runtime path already threaded `base_env` into `docker run --env`; this closes the same gap for the process-based runtime. - New `_bootstrap_with_retry()` helper extracted from `start_supervisor`'s existing retry loop (from #1290), now shared by both `start_supervisor` and `install_supervisor`. - `tests/test_install/test_planner.py`: new tests for all five flags (default-omitted and persisted cases), following the existing `--no-http2` test pattern. - `tests/test_install/test_supervisors.py`: new tests for `--env` propagation into rendered runner scripts, and for `install_supervisor`'s retry-until-success and raise-after-exhausted-retries paths (mirroring the existing `start_supervisor` coverage). Also fixes a pre-existing test's mock that returned `None` from a `subprocess.run` stub — this only worked before because the old bare `bootstrap` call site never inspected the return value; the new `_bootstrap_with_retry()` call does. - `CHANGELOG.md`: added `### Features` and `### Fixed` entries under `Unreleased`. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [x] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ uv run pytest tests/test_install/ tests/test_cli/test_wrap_persistent.py tests/test_cli/test_init_cli.py -q ============================= test session starts ============================== platform darwin -- Python 3.13.13, pytest-9.0.3, pluggy-1.6.0 collected 215 items tests/test_install/test_health.py ... [ 1%] tests/test_install/test_native_installers.py ss [ 2%] tests/test_install/test_paths.py ... [ 3%] tests/test_install/test_planner.py .................. [ 12%] tests/test_install/test_providers.py ................................... [ 28%] ...... [ 31%] tests/test_install/test_runtime.py .................... [ 40%] tests/test_install/test_state.py ..... [ 42%] tests/test_install/test_supervisors.py ......................... [ 54%] tests/test_cli/test_wrap_persistent.py ............................ [ 67%] tests/test_cli/test_init_cli.py ........................................ [ 86%] .............................. [100%] ======================== 213 passed, 2 skipped in 0.57s ======================== $ uv run ruff check headroom/cli/install.py headroom/install/planner.py headroom/install/supervisors.py tests/test_install/ All checks passed! $ uv run mypy headroom/cli/install.py headroom/install/planner.py headroom/install/supervisors.py Success: no issues found in 3 source files ``` ## Real Behavior Proof - Environment: personal fork deployed as a real proxy (macOS launchd service via `headroom install apply`), profile `default`, backend `bedrock` with a named AWS SSO profile. - Exact command / steps: (flags 1 & 2) ran `headroom install apply --backend bedrock --mode token --code-aware --protect-tool-results Bash --bedrock-profile sso-bedrock --env HEADROOM_WORKSPACE_DIR=/Users/<redacted>/.headroom-workspace --env AWS_PROFILE=sso-bedrock --env AWS_REGION=eu-west-1`, then inspected the generated `manifest.json`, the rendered `run-headroom.sh`, and the running launchd job. - Observed result: before this PR, none of `--code-aware`, `--protect-tool-results`, `--bedrock-profile`, or `--env` were accepted flags on `install apply` at all (`Error: No such option`). Reproduced the `--env` gap specifically by running the exact command a launchd job invokes with a stripped environment (no `HEADROOM_WORKSPACE_DIR`, no `AWS_PROFILE`) — it failed to find the manifest; with the interactive shell's env forwarded manually, it started fine. The generated plist had no `EnvironmentVariables` key and `run-headroom.sh` was a bare `exec`, confirming this wasn't a config mistake but a real gap between `install apply`'s flag surface and what a supervisor actually runs with. After this PR, `install apply` with all the flags above produces a launchd job that starts clean, reports healthy, and successfully proxies a real request to Bedrock (200, not just a green health check) using the named AWS profile with no `AWS_PROFILE` env var needed elsewhere. - Exact command / steps: (EIO retry, flag 3) triggered the same EIO race #1290 documents by running `headroom install apply` twice in quick succession against the same profile (the second run's `install_supervisor` bootout+bootstrap lands inside the first run's launchd settle window). - Observed result: before this PR, the second `install apply` occasionally failed outright with `CalledProcessError` from the bare `subprocess.run(..., check=True)` bootstrap call, requiring the manual bootout+`rm` plist+reapply recovery. After this PR (with `_bootstrap_with_retry` in place), the same back-to-back sequence completes successfully every time observed, riding out the EIO window instead of failing. - Not tested: Linux systemd/cron and Windows service/task supervisor paths for the `--env` propagation — verified via the new unit tests (which cover the runner-script rendering directly) but not against a live Linux or Windows machine, since this deployment is macOS-only. ## 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 have updated the CHANGELOG.md if applicable ## Screenshots (if applicable) N/A — CLI/install logic change, no UI surface. ## Additional Notes - "I have made corresponding changes to the documentation" is unchecked: no file in `docs/`, `README.md`, or `CONTRIBUTING.md` documents `install apply`'s flag surface in detail (it's discoverable via `--help`), so there is no existing section to update for the new flags. - Re-derivation note: this PR's `install_supervisor` EIO-retry fix and its `_bootstrap_with_retry` extraction are written directly against current `upstream/main`'s post-#1290 shape of `start_supervisor` (inline retry loop with `_MACOS_BOOTSTRAP_RETRIES`/`_MACOS_BOOTSTRAP_RETRY_DELAY`), not cherry-picked from an older fork commit that predated #1290 — the diff here is intentionally different from what a naive cherry-pick would have produced. - No linked issue number: found via operating a real persistent deployment on a personal fork, not filed as a `headroomlabs-ai/headroom` issue first. Checked `gh pr list --search` for "install apply flags/env" and "bootstrap EIO"/"install_supervisor bootstrap retry" — no open or merged coverage found beyond #1290 (which fixes `start_supervisor` only, a different call site from the one this PR fixes). Co-authored-by: JerrettDavis <mxjerrett@gmail.com> |
||
|---|---|---|
| .. | ||
| test_health.py | ||
| test_native_installers.py | ||
| test_paths.py | ||
| test_planner.py | ||
| test_providers.py | ||
| test_runtime.py | ||
| test_state.py | ||
| test_supervisors.py | ||