Appearance
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:
- Port owned by this worktree → reuse it as-is. Print
ACQUIREDwith the existing PID. No new process is started. - Port owned by another worktree → skip, increment.
- Port is free → attempt to start the server.
- Server binds successfully → print
ACQUIRED. - 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:
| Output | Meaning |
|---|---|
FREE | No listener on this port |
MINE <pid> | This worktree's server — reused on start |
OTHER <pid> <cwd> | Another worktree's server — never touched |
Environment variables
| Variable | Default | Purpose |
|---|---|---|
SERVE_MAX_ATTEMPTS | 50 | Max ports to try |
SERVE_SETTLE_MS | 3000 | Wait time for bind |
SERVE_WORKTREE | auto | Override worktree root |
SERVE_OPEN | 1 | Open 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.