Skip to content

feat: global port conflict detection for concurrent headroom wrap sessions#1121

Open
hareeshkar wants to merge 3 commits into
headroomlabs-ai:mainfrom
hareeshkar:feat/global-port-conflict-detection
Open

feat: global port conflict detection for concurrent headroom wrap sessions#1121
hareeshkar wants to merge 3 commits into
headroomlabs-ai:mainfrom
hareeshkar:feat/global-port-conflict-detection

Conversation

@hareeshkar

@hareeshkar hareeshkar commented Jun 18, 2026

Copy link
Copy Markdown

Description

When one headroom proxy is already running (e.g. headroom wrap claude on
port 8787), starting another agent (headroom wrap codex, headroom wrap aider) fails because port 8787 is occupied. This PR adds global port
conflict detection so any agent started second automatically uses the
next available port — no manual --port flags needed.

Integrated into all three proxy startup paths used by every agent.

Type of Change

  • New feature (non-breaking change that adds functionality)

Changes Made

  • _find_available_port(start_port, max_attempts=10) helper in wrap.py
  • Integrated into _launch_tool, _run_proxy_only_watcher, and claude()
  • All paths guarded by not no_proxy so --no-proxy bypasses detection
  • 13 new tests: 9 unit + 4 integration

Testing

  • Unit tests pass
  • New tests added

Test Output

tests/test_cli/test_port_conflict.py   13 passed
CI precheck: 110 passed, 4 skipped
Total: 123 passing

Real Behavior Proof

  • Environment: macOS arm64, Python 3.10, headroom v0.27.0
  • Exact command / steps: headroom wrap claude in terminal 1 (port 8787), then headroom wrap codex in terminal 2
  • Observed result: Codex prints "port 8787 in use — using 8788", starts on port 8788. Zero user intervention.
  • Not tested: Windows, Linux. More than 10 concurrent agents.

Review Readiness

  • I have performed a self-review
  • This PR is ready for human review

Additional Notes

Add _find_available_port(start_port, max_attempts) that skips ports
occupied by another headroom proxy. Integrated into all three code paths:

Path A — _launch_tool (aider, copilot, codex, goose, openhands)
Path B — _run_proxy_only_watcher (cursor, cline, continue)
Path C — claude() (Claude Code — custom proxy lifecycle)

When port 8787 is occupied by claude, codex/aider/copilot auto-start
on 8788. Works for all agents, all combinations, all orderings.

Verification:
- 9 unit tests: _find_available_port behavior (free, occupied, gap,
  range exhaustion, port range message)
- 4 integration tests: source inspection verifies _find_available_port
  appears before _ensure_proxy in all 3 code paths, guarded by
  'not no_proxy' condition
- 110 CI precheck tests unaffected
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

PR governance

This PR follows the template and is marked ready for human review.

@github-actions github-actions Bot added the status: needs author action Pull request body or readiness checklist still needs author updates label Jun 18, 2026
@github-actions github-actions Bot added status: ready for review Pull request body is complete and the author marked it ready for human review and removed status: needs author action Pull request body or readiness checklist still needs author updates labels Jun 18, 2026

@JerrettDavis JerrettDavis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The port-conflict change needs cleanup before merge. The PR adds local Serena project files (.serena/.gitignore, .serena/project.yml) and unrelated uv.lock churn for package version/litellm markers. Those are outside the feature behavior and should be removed from this PR so the review and merge only cover the port-selection change. After that, the remaining wrap.py/test changes can be reviewed on their own merits.

hareeshkar added a commit to hareeshkar/headroom that referenced this pull request Jun 19, 2026
De-duplicate: _find_available_port and its integration into
_launch_tool, _run_proxy_only_watcher, and claude() belong
only in PR headroomlabs-ai#1121 (global-port-conflict-detection). OpenCode
handles port assignment through its own bounded loop with
no_proxy=True to _launch_tool.

Removed:
- _find_available_port() function definition
- 3 integration points (_launch_tool, _run_proxy_only_watcher, claude())
- tests/test_cli/test_port_conflict.py (13 tests → lives in headroomlabs-ai#1121)

Retained:
- OpenCode bounded port loop with max 10 attempts
- All 57 original opencode tests
@hareeshkar hareeshkar requested a review from JerrettDavis June 22, 2026 00:54
@github-actions github-actions Bot added status: ci failing Required or reported CI checks are failing and removed status: ready for review Pull request body is complete and the author marked it ready for human review labels Jun 23, 2026

@JerrettDavis JerrettDavis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The port selection implementation is close, but this PR still has unrelated uv.lock churn in the current diff. The lockfile changes bump the local package version and alter LiteLLM dependency markers; that is not part of global port conflict detection and should not ride along with this feature.

Please remove the uv.lock changes from this PR so the diff is limited to headroom/cli/wrap.py and the focused port-conflict tests. Once the scope is clean, the wrap-path behavior and CI can be reviewed/merged without accidentally changing dependency metadata.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: ci failing Required or reported CI checks are failing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants