Skip to content
Machine Learning
Skill

/dev-server

Manage Next.js dev servers across worktrees. Start, stop, and read logs from dev servers. Agents can access logs from any running session, regardless of who started it.

BOOST
From plugin
civitai
7.3k48 skills15 agents3 commands
Install
$ npx -y skills add civitai/civitai --skill dev-server --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/dev-server

Context preview

The summary Claude sees to decide when to auto-load this skill.

Manage Next.js dev servers across worktrees. Start, stop, and read logs from dev servers. Agents can access logs from any running session, regardless of who started it.

SKILL.md

dev-server.SKILL.md
name: dev-server
description: Manage Next.js dev servers across worktrees. Start, stop, and read logs from dev servers. Agents can access logs from any running session, regardless of who started it.

Dev Server Skill

Centralized management of Next.js dev servers across multiple git worktrees. The daemon handles port allocation, environment variable injection, and log aggregation so that any agent can access dev server logs regardless of who started the server.

Quick Start

# Check what's running
node .claude/skills/dev-server/cli.mjs status

# Start a dev server for current worktree
node .claude/skills/dev-server/cli.mjs start

# Start for a specific worktree
node .claude/skills/dev-server/cli.mjs start /path/to/worktree

# Start with a service on production (see Env modes)
node .claude/skills/dev-server/cli.mjs start /path/to/worktree --prod buzz

# View logs
node .claude/skills/dev-server/cli.mjs logs <session-id>

# Stop a session
node .claude/skills/dev-server/cli.mjs stop <session-id>

**Checking if server is ready:** After starting, poll the session status to check `ready: true`. The daemon marks sessions ready either via configured health check endpoint or by detecting "Ready" patterns in logs.

Which node the daemon runs on — and why it is sticky

The daemon is spawned with `process.execPath` (`startDaemon` in `cli.mjs` and `console.mjs`), i.e. **whatever node ran the CLI verb that first started it**. It then passes its own environment down to every `next dev` it supervises. So the node you happened to have on `PATH` the first time you typed any command above is the node the whole tree runs on, until someone shuts the daemon down — and nothing records which one that was.

That is not academic. Measured on a dev box: the daemon was running on ambient node **26.7.0**, while `.nvmrc` pins **24.19.0**, `package.json` declares `engines.node: ">=24.0.0 <25"`, and production is built on `node:24.19.0-alpine3.24`. It had been started from a shell outside the dev shell, and that shell also had no `pnpm` at all — which silently disables the daemon's own auto-install path (it shells out to `pnpm install` / `pnpm run db:generate` when it sees the lockfile or the schema move).

**The fix, whatever your setup: start it from a shell whose `node --version` matches `.nvmrc` and which has `pnpm` on `PATH`.** `nvm use` at the repo root gives you both.

**On NixOS (optional)** the flake wrapper does that for you — it pins node to the same version `.nvmrc` names, puts pnpm on `PATH`, and exports the Prisma engine paths NixOS needs:

nix run .#dev-server -- status
nix run .#dev-server -- start
nix run .#dev-server -- logs <session-id>

Every `node .claude/skills/dev-server/cli.mjs …` invocation elsewhere in this document takes the same subcommands — the wrapper only decides which node runs them, so nothing here depends on having Nix.

Either way, **check what you have got** before trusting a session:

# the daemon's real interpreter, not the one you assume.
# the pid file belongs to the skill dir the daemon RUNS FROM, which is the primary checkout — a
# relative path here reads the wrong tree's file, or none, when you are standing in a worktree.
readlink -f /proc/$(cat <primary-checkout>/.claude/skills/dev-server/daemon.pid)/exe

Changing node means restarting the daemon — `cli.mjs shutdown`, then start it again from the right shell. A running daemon will not pick up a new `PATH`.

A second daemon: `DEV_DAEMON_PORT`

The daemon lives on `127.0.0.1:9444`. Set `DEV_DAEMON_PORT` to stand another one beside it:

DEV_DAEMON_PORT=9555 node .claude/skills/dev-server/cli.mjs status

The CLI, the console, `scripts/test-unit-run.mjs` and the daemon itself all resolve the port through `scripts/daemon-port.mjs`, and a daemon a client spawns inherits that client's environment — so no two of them can disagree about where it is. Until 2026-08-19 the daemon did not read the variable at all, and setting it pointed the client at a port nothing was serving.

The variable is only a default for the daemon; an explicit `node scripts/daemon.mjs --port <port>` still wins. Each daemon owns the shared `daemon.pid`, so the last one started is the one that file names.

Never curl a dev port — `probe` instead

node .claude/skills/dev-server/cli.mjs probe /home

A dev server that has stopped serving properly does not refuse connections. It accepts and answers slowly, or accepts and never answers — so an unbounded `curl` sits there until the 300s tool timeout and returns nothing you can act on. Chaining a few in one shell call is how ten minutes disappear. A `PreToolUse` hook blocks unbounded requests at dev ports for this reason; `--max-time` is the escape hatch if you really want curl.

`probe` requests the route twice with a hard budget, reads the session's own log for those two requests, and returns a verdict with the matching remedy. It always terminates.

UPSTREAM-SLOW  http://localhost:3000/home
  UPSTREAM-SLOW — the framework is fine; time is spent in application code (database, tunnel, cache).
  first : 200 in 8.10s  [next.js 30ms | application-code 8.10s]
  repeat: 200 in 8.10s  [next.js 31ms | application-code 8.10s]
  -> Not a cache problem — do NOT purge, it will not help. [...]

Exit code is 0 for `ok`/`cold` and 1 for everything else, so it substitutes for a curl in a check.

Two verdicts worth knowing before you meet them. **`stopped-answering`** means the first request was served and the second was not — the process is up and something inside it has parked, so starting another session is the wrong move and the remedy says so. And a probe may add `note: more than one request to this route in the window`: `probe` tags its own request with a `?__probe=` nonce so it can find its own log line, but a route that already has a query string — and every `/api/trpc/*` route, whose handlers parse their query — falls back to matching on

Read more
Ships withcivitai

A repository of models, textual inversions, and more

Get the whole plugin

Other skills on civitai.