Skip to content

OSS landscape: coordinating local dev ports across agent sessions

Verified 2026-09-13 by fetching each repository or docs page. Stars and dates are as of that day.

What we looked for

Three capabilities, because the problem has three halves:

  1. Allocation and ownership. Something an agent can ask "which port may I use?" and "who holds 5432?", that survives across sessions.
  2. Agent integration. A way for Claude Code (and Codex, and humans) to learn the answer without being told, ideally at session start.
  3. Visualization. A view for the human of what is listening, who owns it, and what the agreed ranges are.

Evidence from this Mac

  • 34 Claude Code project contexts under ~/.claude/projects, including parallel .claude/worktrees checkouts of chancery, llm-council and swe-ai-ml-kb. VS Code Insiders helper processes also run; their truncated lsof name Codex20- is Code - with an encoded space, not OpenAI Codex, which is not installed.
  • Docker runs through Colima 0.10. Every published container port shows up in lsof under one ssh process (pid 503) whose cwd is whichever repo ran colima start. Compose-launched containers carry com.docker.compose.project.working_dir labels; docker run containers (penguin-mailpit, penguin-platform-db) carry nothing.
  • Only the standalone docker-compose 5.1.4 is installed. The docker compose plugin is absent. !override and ${VAR:-default} both verified with the standalone binary.
  • Node 20.20 (no node:sqlite), Python via uv, Rust, mise. No Caddy, no direnv, no process-compose.
  • macOS ControlCenter listens on 5000 and 7000 (AirPlay receiver). macOS ephemeral ports start at 49152.
  • ~/.claude/settings.json already runs Node-based hooks (graft, iTerm status) on SessionStart, PreToolUse, PostToolUse, Stop and SessionEnd. There is no global ~/.claude/CLAUDE.md.
  • Bash-tool subprocesses inherit CLAUDE_CODE_SESSION_ID, CLAUDECODE=1, CLAUDE_PID and more. For own-user processes, ps -E -p PID -o command= exposes that environment without sudo. The Colima VM processes (limactl, pids 478/480) were attributed this way to the skills-telemetry session that started them.

Port contention declared in repo configs

PortConventional ownerDeclared byLive right now
3000Next, Eleventy, Docusaurus, Grafanamonstrous-media, conductor-website, amiable-docusaurus, amiable-docusaurus-v1, docusaurus-plugin-stentorosaur, breach-resolve (frontend and Grafana), standards-telemetry, skills-telemetry, llm-council observability exampleskills-telemetry Grafana (Docker)
3001skills-telemetry (Langfuse), breach-resolve (Grafana remap), 36-inch-platform (BASE_URL)skills-telemetry Langfuse
5432Postgreslearnlock-studio, breach-resolve, 36-inch-platform compose, skills-telemetry, standards-telemetry, sre-agent and heptara DSNsskills-telemetry Postgres
543336-inch-platform .env, breach-resolve test composepenguin-platform-db
5439(none)cith-watch Postgres
5173Vitesightline, cith-watch, conductor-gui (two copies)
8000FastAPI / uvicornllm-council, breach-resolve gateway
8001–8005breach-resolve microservices
8787wranglersightline, cith-watch
9090Prometheusbreach-resolve (twice), standards-telemetry, skills-telemetry, llm-council exampleskills-telemetry
4317 / 4318OTLPbreach-resolve, standards-telemetry, skills-telemetryskills-telemetry
3200Tempostandards-telemetry, skills-telemetryskills-telemetry
6379 / 6380Redisbreach-resolve (6379), breach-resolve test (6380)cith-watch Redis on 6380
1025 / 8025Mailpit36-inch-platformpenguin-mailpit
5000 / 7000macOS AirPlayControlCenter (system)

Four repos ship a near-identical observability stack (Grafana, Prometheus, OTel collector, Tempo) on the same ports. Only one can run at a time, and whichever session starts it "wins" without anyone recording that.

Named-host proxies and port managers

ToolActivityStarsAssigns ports?Queryable state?Non-HTTP?Verdict
portless (Vercel Labs)v0.15.6, Aug 202612.4kYes: random PORT in 4000–4999, --port injected for Viteportless list, ~/.portless/routes.jsonNo (HTTPS proxy; alias name port for anything else)Best named-URL layer. Its own allocator conflicts with fixed blocks unless used via alias.
outportv0.44 alpha, Sep 202622Yes: deterministic per project/instance, written to .envoutport ports --json, dashboard at outport.testYes (Postgres/Redis)Closest end-to-end match, single-maintainer alpha, hash-based not block-based.
portmarshalv0.8.1, Sep 20264Yes: claim, run --preferlist --json, whois --json, ~/.portmarshal/registry.jsonYesBest "who owns this port" story; refuses to kill another agent's service. Seven weeks old.
port-selectorv0.10, Aug 20264Yes: stable per (dir, name), 24 h freeze--list table, YAML registryNumbers onlySimplest agent-safe allocator; README ships a CLAUDE.md snippet.
portreev0.5, Aug 202625Yes: FNV32 hash per worktree/servicels --jsonNumbers; proxy HTTPWorktree-specific, branch.localhost routing.
portzillaSep 20261Yes: lease file with PID and --session $CLAUDE_CODE_SESSION_IDMCP serverYesKill-guard, session-aware. Tiny.
porta / port-authorityJul 20260YesPer-user registry, OS file lock, daemonlessYesRight shape for a ledger; unused.
PortNannySep 20263YesMCP (reserve_port, whois_port, free_port) + menubarYesAttributes servers to agents via CLAUDECODE=1; Claude plugin install.
port-daddySep 20262YesHTTP API :9876, 180 MCP toolsYesHeavy daemon, FSL licence. Overkill.
hotelOct 202310kYesGET localhost:2000/_/servers JSONNoRight shape, dormant three years, PAC-file proxy.
localiasv3.0, Nov 20251.5kNo (alias → port you choose)localias list, YAMLNo (Caddy)Manual name map with TLS.
DDEVv1.25.4, Sep 20263.8kYes, per project on ddev startddev list -j, describe -jYesExcellent registry but Docker-only, PHP/Node web.
devenvv2.3.1, Sep 20267.6kYes: ports.<n>.allocate, strict_portsNot externally queryableYesNix-only.
process-composev1.122, Aug 20262.8kNoREST /processes, MCP pc_process_portsReports listenersPer-project orchestrator; its control port 8080 itself collides.
overmind / foreman / hivemind2024–20253.7k / 6.2k / 1.2kConvention: base 5000 + 100 per Procfile lineNoneNumbersWithin one Procfile only.
Caddy *.localhostv2.11.475.7kNoAdmin API :2019With layer4 pluginSolid manual proxy; auto-trusted local certs.
mise / direnvactive33.9k / 15.4kNo (glue: PORT = "{{ … }}", export PORT=$(allocator))mise env --jsonNumbersWhere to pin a value, not where to allocate one.
get-port / portfinder / detect-portactive929 / 899 / 392Find-a-free-port onlyNone across processesNumbersBuilding blocks.
Tiltv0.37.710kNo (declared port_forwards)tilt get portforwards -o jsonYes (k8s)Kubernetes-centric.
puma-dev, Laravel Valetslow / active1.8k / 2.6kNovalet proxiesNoRack / PHP ecosystems.

Session managers and orchestrators

ToolPort handling
ConductorCONDUCTOR_PORT = first of a 10-port block per local workspace; scripts do pnpm dev --port $CONDUCTOR_PORT, $((CONDUCTOR_PORT + 1)) for a second service. Allocation algorithm undocumented.
SupersetDetects ports by scanning process trees; docs suggest you DIY a ~/.superset/port-allocations.json in setup/teardown scripts.
Vibe KanbanScrapes http://localhost:NNNN from the dev-server script's stdout and proxies it; conflicts are yours to solve. Sibling dev-manager-mcp hands out sequential ports from 3010.
Crystal → Nimbalyst, claude-squadNothing; claude-squad issue #260 requests port isolation hooks. Nimbalyst blog: "assign a port range per worktree" by hand.
Sculptor, Cursor cloud agents, Codex cloudContainer per agent; forwardPorts / ports: [{port, name}]. Isolation removes the problem rather than coordinating it.
tfriedel/claude-worktree-hooksWorktreeCreate hook: md5(branch) % 6900 + 3100 → DEV_PORT in .env.local.

Visualizers

ToolStars / activityPIDcwd or projectLiveVerdict
lsof -nP -iTCP -sTCP:LISTENbuilt inyesyes, own-user, no sudo (lsof -a -p PID -d cwd -Fn)poll ≈0.05 sBackbone. Misses root listeners.
netstat -anv -p tcpbuilt inyes, including root'snopollMerge with lsof for root processes.
lsoff249, Sep 2026yesCWD and PROJECT columnsTUI, 2 sBest OSS CLI/TUI today; no range grouping, no Docker attribution.
procs6.2kyesports yes, WorkDir not on macOS--watchPartial.
port-light54, Sep 2026nocompose project namesyesOnly true range/traffic-light grid; Linux/Docker oriented.
port-collision-radar2yesno4 sOnly tool with owner-change / squatter alarms.
Portsly, port-tools23 / 0yesyes (port-tools: repo/worktree/branch)yesMenubar attempts; zero traction.
Portpal505, Apr 2026yesmanifest crawlyesD3 force graph, not a range map.
killport, kill-port, fkill1.8k / 570 / 7ksomenoKill-only; killport is Colima/OrbStack aware.

No OSS tool combines a port-range map, PID, cwd and Docker/Colima attribution on macOS.

Claude Code platform facts

  • Hooks: common input carries session_id, transcript_path, cwd, hook_event_name. SessionStart may return hookSpecificOutput.additionalContext and may append export VAR=… lines to the file named by CLAUDE_ENV_FILE, which then apply to every later Bash call. SessionEnd receives a reason. PreToolUse can deny with a reason (not used here by decision). WorktreeCreate / WorktreeRemove exist since v2.1.50. Hooks configured in ~/.claude/settings.json apply to all projects.
  • Environment variables: CLAUDE_CODE_SESSION_ID is exported to Bash, hook and stdio-MCP subprocesses (CHANGELOG 2.1.132, 2.1.154) and matches the hook session_id.
  • Desktop preview servers: .claude/launch.json with port, env, and autoPort: true (finds a free port and passes it as PORT). Issue #86039 notes orphaned child servers holding ports after Stop.
  • MCP user scope: claude mcp add --scope user makes a stdio server available in every project.

Verified mechanisms (local tests, 2026-09-13)

MechanismResult
docker ps --format '{{.Label "com.docker.compose.project.working_dir"}}'Attributes compose containers to their repo directory. docker run containers return empty.
ps -E -p PID -o command= on a Node process from another Claude sessionShows CLAUDE_CODE_SESSION_ID and CLAUDECODE without sudo.
docker-compose -f base.yml -f override.yml config with ports: !overrideReplaces the hardcoded 5432:5432 with 25432:5432; no repo edit needed.
DB_PORT=25432 docker-compose -f env.yml config with "${DB_PORT:-5432}:5432"Substitutes correctly.
lsof -nP -iTCP -sTCP:LISTEN timing≈0.05 s; cwd lookup for all listeners ≈0.12 s in two calls.

Bottom line

  • For named HTTP URLs there is one mature option: portless. Use its alias command so it proxies without allocating.
  • For cross-session ownership of any TCP port nothing mature exists. The nearest are alpha, single-maintainer tools that model ports by hashing rather than by human-readable blocks, and none reconciles a registry against Docker/Colima reality.
  • For seeing what is running lsoff is the best off-the-shelf CLI today, and the only range-map UI (port-light) is Linux/Docker oriented.
  • The gap that matters, a ledger plus a reconciler plus a range map with session attribution, is small enough to build and is not on offer anywhere. See DESIGN.md.

Released under the MIT licence.