Skip to content

Dev Serve

Start a dev server on a safe port. One command, fully automated — handles port allocation, worktree ownership, and race conditions.

Replaces the old ensure-dev-server script. Use dev-serve whenever starting a dev server, especially in multi-worktree or multi-agent environments.

Usage

bash
dev-serve <port> <command>

The allocated port is exported as $PORT. Most frameworks read it automatically. If yours needs an explicit flag, reference $PORT:

bash
dev-serve 3000 "npm run dev"
dev-serve 3000 "vite --port $PORT"
dev-serve 8000 "python -m http.server $PORT"

On success the script prints:

ACQUIRED <port> <pid>
http://localhost:<port>

…and opens the URL in the default browser (suppress with SERVE_OPEN=0).

The script lives alongside this file as an executable: skills/dev-tasks/dev-serve

How it works

From the preferred port, the script walks upward:

  1. Port owned by this worktree → reuse it as-is. Print ACQUIRED with the existing PID. No new process is started.
  2. Port owned by another worktree → skip, increment.
  3. Port is free → attempt to start the server.
  4. Server binds successfully → print ACQUIRED.
  5. Server dies on startup (race lost) → increment, retry.

A free port is advisory. The start attempt is the real lock. If another process grabs the port between the check and the bind, the script detects the failure and moves on.

dev-serve never kills automatically — not even a server it owns. If your own server is genuinely broken (hung, crashed, stuck compiling), kill the PID yourself and run dev-serve again.

Port ownership

Ownership is determined by comparing the listener process's working directory against the current worktree root (from git rev-parse --show-toplevel). If the process's cwd is inside the current worktree, it's MINE. Otherwise it's OTHER.

dev-serve reuses MINE ports and skips OTHER ports. It never kills either.

Diagnostic

Inspect a port without starting anything:

bash
dev-serve check <port>

Prints one of:

OutputMeaning
FREENo listener on this port
MINE <pid>This worktree's server — reused on start
OTHER <pid> <cwd>Another worktree's server — never touched

Environment variables

VariableDefaultPurpose
SERVE_MAX_ATTEMPTS50Max ports to try
SERVE_SETTLE_MS3000Wait time for bind
SERVE_WORKTREEautoOverride worktree root
SERVE_OPEN1Open the URL in a browser on success (0 to suppress)

Rules

The core principle: dev-serve never kills. A listening server is assumed to be doing its job until the human decides otherwise. Running dev-serve against an existing MINE port is a no-op that returns the URL.

Never blind-kill

Never run kill $(lsof -ti tcp:3000). Never run killall node or pkill -f vite. If a server is genuinely broken and you need it gone, target its specific PID by hand, then run dev-serve again.

Never assume "port in use" means your server

Another worktree, another agent, or a completely unrelated process may be listening. dev-serve classifies every occupied port before acting.

Never check-then-start as two steps

A port that's free when you check may be taken when you bind. dev-serve treats the start attempt as the real lock and retries on failure. Don't write your own two-step check-then-start logic.