persistent-localhost · git:20260903.68f1c83 · 2026-09-03 · sha256 00e01cc9bbeff6f6
persistent-localhost git:20260903.68f1c83A
Immutable. This exact content never changes and is served at /api/v1/blob/00e01cc9bbeff6f6.
--- name: persistent-localhost description: Start, stop, restart, and inspect durable localhost dev servers on macOS as user LaunchAgents, so they outlive the agent's shell and auto-restart on crash. Use whenever an agent must run a dev server, API, or any long-lived local process on a port: "start the dev server", "run it on localhost", "keep the server running", "the server keeps dying", "restart the app on port 5111". Never start servers with nohup, &, disown, setsid, or run_in_background; those die with the shell or become unfindable orphans. --- # Persistent localhost servers (macOS launchd) One script does everything. Resolve `scripts/persistent-localhost.sh` relative to this `SKILL.md`, never from the current directory. ## Workflow 1. Check what already exists: ```bash scripts/persistent-localhost.sh list ``` 2. Start the server. `--name` is the handle. `--port` enables readiness checks and duplicate handling. `--dir` defaults to the current directory. ```bash scripts/persistent-localhost.sh start --name myproj --port 5111 --cmd "./bin/serve" ``` The command returns only after the port is listening, or fails with the last 20 log lines. Report the printed `URL=` to the user. 3. Inspect, restart, read logs, stop: ```bash scripts/persistent-localhost.sh status myproj scripts/persistent-localhost.sh restart myproj # after editing code that has no reloader scripts/persistent-localhost.sh logs myproj 100 scripts/persistent-localhost.sh stop myproj ``` ## Hot reload Prefer the framework's own reloader in `--cmd` (`flask run --debug`, `vite`, `uvicorn --reload`, `node --watch`). If the server has none, add `--watch`. It wraps the command in `watchexec -r` on the working directory. Never use both. Requires `brew install watchexec`. ## Rules the script enforces - Runs as `com.persistent-localhost.<name>` in the user `gui` domain via `launchctl bootstrap`. Restart uses `kickstart -kp`, stop uses `bootout`. No deprecated `load`, `start`, `stop`, no broad `pkill`. - `KeepAlive` only on crash. A clean exit stays down. Throttle 5s so a syntax error cannot spin. - Port already taken by the same app (our own label, or a stray process with the same working directory) is replaced. A different app is left alone and the next free port is used. Read the printed `PORT=`. - The caller's `PATH` and a `PORT` variable are passed into the job. Add more with `--env KEY=VAL`. - Logs: `~/Library/Logs/persistent-localhost/<name>.log`. State: `~/Library/Application Support/persistent-localhost/<name>.state`. Plist: `~/Library/LaunchAgents/`. ## Fallback If `launchctl bootstrap` fails, say so and stop. Do not fall back to `nohup` or `&`.