{"slug": "show-hn-portmarshal-guard-dev-servers-from-cross-agent-stops", "title": "Show HN: PortMarshal – Guard dev servers from cross-agent stops", "summary": "PortMarshal, a new open-source command-line tool for macOS and Linux, prevents parallel coding agents from accidentally stopping each other's local development servers by mapping TCP listeners to their owning process, project, and agent. The tool, installable via npm, detects port drift, coordinates sticky port claims, and blocks one agent from killing another agent's active service by default. It supports cooperative agents like Claude Code, Cursor, VS Code, and Docker, and requires Node.js 18.17 or newer.", "body_md": "Know which coding agent owns a local dev server — and stop the wrong one from being killed.\n\n**English** | [简体中文](/worsher/portmarshal/blob/main/README.zh-CN.md)\n\nPortMarshal is an agent-aware ownership and safety layer for local development services on macOS and Linux. It maps attributable TCP listeners to their PID, project directory, and launching agent; coordinates sticky port claims; detects port drift; and blocks one agent from stopping another agent's active service by default.\n\n```\nnpm install -g portmarshal\nportmarshal list\nportmarshal whois 3000\n```\n\nThe last command inspects the process, project, and agent behind a dev server already listening on port 3000.\n\nParallel coding agents create three recurring problems:\n\n**Silent drift**— an agent expects port 3000, but the framework silently starts on 3001.** Detached services**— a session exits while its dev server keeps listening.** Friendly fire**— one agent frees a port by stopping another agent's service.\n\nPortMarshal scans first and coordinates second. Existing listeners do not need to be launched through PortMarshal to be discovered. Cooperative agents gain stable claims and stronger ownership signals, while uncooperative services still appear when the operating system exposes their process metadata.\n\n```\nnpm install -g portmarshal\nportmarshal --help\n```\n\nRequires Node.js 18.17 or newer. The runtime has no npm dependencies.\n\n```\nnpm uninstall -g @worsher/portscout @worsher/portmarshal\nnpm install -g portmarshal\n```\n\nOn first use, PortMarshal copies an existing `~/.portscout/registry.json`\n\ninto `~/.portmarshal/registry.json`\n\n, preserving sticky claims without deleting the old data.\n\n| Command | What it does |\n|---|---|\n`portmarshal list [--json] [--all] [--project .]` |\nList listeners with project, source, and state: active, reserved, unregistered, or drift |\n`portmarshal whois <port> [--json]` |\nInspect one port: PID, project directory, full command, agent or service source |\n`portmarshal claim <name> [--prefer N] [--range A-B]` |\nAllocate a cooperative sticky port claim; stdout contains only the port number |\n`portmarshal release <name>` |\nRelease a claim without stopping its process |\n`portmarshal stop <port|name> [--force|--gui]` |\nStop a service behind the ownership guard |\n`portmarshal gc [--kill-detached]` |\nReap stale claims and review or stop detached service candidates |\n`portmarshal watch` |\nRefreshing terminal dashboard; press `q` to quit |\n`portmarshal menubar [--install]` |\nSwiftBar menu with guarded click-to-stop actions |\n\nTypical agent startup:\n\n```\nPORT=$(portmarshal claim web --prefer 3000)\nnpm run dev -- --port \"$PORT\"\n```\n\nA claim is a cooperative lease, not an operating-system socket reservation. PortMarshal revalidates a previous claim before reusing it: a port must still be free or be attributable to the same project. There is still an unavoidable handoff window between returning a free port and the application binding it.\n\nPortMarshal follows the process parent chain to identify `claude-code`\n\n, `cursor`\n\n, `antigravity`\n\n, `vscode/electron`\n\n, `terminal`\n\n, `docker`\n\n, and `pm2`\n\n. PM2-managed listeners are enriched from `pm2 jlist`\n\n, displayed as `pm2:<app-name>`\n\n, and attributed to the application's configured cwd; the full PM2 environment is never retained. For published Docker ports, PortMarshal inspects running-container metadata: shared Docker Desktop listeners are split by container, the source is shown as `docker:<compose-project>/<service>`\n\n, and the host project directory is recovered from Compose, Dev Container, or bind-mount metadata. If managed-runtime metadata is unavailable, attribution safely falls back without inventing ownership. PortMarshal also recognizes `launchd:<label>`\n\non macOS and `systemd:<unit>`\n\non Linux. A process reparented to PID 1 without a recognized manager is labeled `detached`\n\n— this is a review signal, not proof that the process is abandoned.\n\n| Target | Default `stop` behavior |\n|---|---|\n| PM2 application owned by the caller's project/claim | Run `pm2 stop <id>` ; never signal a managed child that PM2 would restart |\n| Docker container owned by the caller's project/claim | Run `docker stop` for that container; never signal the shared Docker backend |\n| Detached service or a service owned by the caller's project/claim | Stop with SIGTERM, then SIGKILL after 3 seconds if needed |\n| Another active service | Block, print attribution, and exit with code 3 |\n\n`--force`\n\noverrides the guard after review. On macOS, `--gui`\n\nasks through a native confirmation dialog.\n\nPortMarshal can only attribute listeners whose process metadata is visible to the current user. For example, Linux `ss`\n\noutput without PID information is not invented or guessed; those rows are omitted.\n\nAdd this policy to `AGENTS.md`\n\n, `CLAUDE.md`\n\n, or your editor's agent rules:\n\n```\n- Before starting a dev server, get a port with `PORT=$(portmarshal claim <service> --prefer <default>)`.\n- Diagnose conflicts with `portmarshal list --project . --json` and `portmarshal whois <port> --json`.\n- Stop services with `portmarshal stop <port>`; exit code 3 means another active service owns it, so show the attribution and ask before using --force.\n```\n\nA ready-to-copy Claude Code skill lives in [ integrations/claude-code/skills/portmarshal](/worsher/portmarshal/blob/main/integrations/claude-code/skills/portmarshal).\n\nand`lsof`\n\n`ss`\n\nexpose sockets and processes; PortMarshal adds project/agent attribution, claims, drift detection, and stop policy.[Sonar](https://github.com/RasKrebs/sonar)is a broad localhost and Docker management CLI; PortMarshal focuses on cross-agent ownership and guarded actions.[Portless](https://github.com/vercel-labs/portless)launches apps behind stable named local URLs; PortMarshal can inspect services whether or not it launched them. The tools can be used together.\n\n```\npnpm test\npnpm smoke\npnpm build\n```\n\nGitHub Actions runs build, unit tests, and a real listener smoke test on macOS and Linux. Tagged releases publish to npm with provenance.\n\nDesign: [ docs/specs/2026-07-16-portmarshal-design.md](/worsher/portmarshal/blob/main/docs/specs/2026-07-16-portmarshal-design.md) ·\n\n[Changelog](/worsher/portmarshal/blob/main/CHANGELOG.md)\n\nmacOS and Linux · Node.js ≥ 18.17 · zero runtime dependencies · MIT", "url": "https://wpnews.pro/news/show-hn-portmarshal-guard-dev-servers-from-cross-agent-stops", "canonical_source": "https://github.com/worsher/portmarshal", "published_at": "2026-07-22 13:03:28+00:00", "updated_at": "2026-07-22 13:22:44.510163+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["PortMarshal", "Node.js", "Claude Code", "Cursor", "VS Code", "Docker", "PM2"], "alternates": {"html": "https://wpnews.pro/news/show-hn-portmarshal-guard-dev-servers-from-cross-agent-stops", "markdown": "https://wpnews.pro/news/show-hn-portmarshal-guard-dev-servers-from-cross-agent-stops.md", "text": "https://wpnews.pro/news/show-hn-portmarshal-guard-dev-servers-from-cross-agent-stops.txt", "jsonld": "https://wpnews.pro/news/show-hn-portmarshal-guard-dev-servers-from-cross-agent-stops.jsonld"}}