Skip to content

harn servers

Terminal window
harn servers [list|stop|gc|logs|open|register|unregister|touch]

Many tools start a local web server: preview UIs, tuning panels, review pages, dashboards, tunnel gates. Each one used to keep its own record of what it started, so nothing could list them together and nothing stopped the ones left behind. servers reads one registry under .harnery/servers/ that every such tool writes to.

  • service: one long-running copy, such as a dashboard or a tunnel. Listed and stoppable by hand. Never stopped automatically.
  • session: one copy per piece of work, such as a preview for one scene. Eligible for gc once the agent that started it is gone and nobody has used it for the idle window.
Terminal window
harn servers
harn servers list --json
harn servers list --no-scan

The list shows each server’s id, URL, label, starting agent, and use. Records of servers whose process has exited are removed as they are read. Open connections count as use, so a browser tab left open on a preview keeps it alive.

A server state is running, dead, other-host, or unknown. A same-host record is unknown when its process generation cannot be verified, including when its start token is missing. The list keeps that record and marks it [unknown]. Unknown records remain registered, reserve their configured port, and cannot become unregistered listeners through the scan.

The list also scans listening sockets. A process whose working directory or command line is inside the project, that listens on a port, and that is not a registered server or a descendant of one appears under Unregistered servers. Browser automation and editor servers are excluded. The scan uses procfs on Linux and lsof on macOS; other platforms report that it did not run.

Running tunnels register when they start. A tunnel with only an older tunnel state file has no recorded process-generation evidence, so the list does not automatically register it. If the scan finds it, it appears as an unregistered listener for explicit inspection or adoption.

The tunnel registry records the gate process. harn servers stop tunnel-<name> controls that gate only. Saved provider-helper PIDs have no recorded start tokens, so registry stop leaves those helpers alone.

Terminal window
harn servers stop <id> [<id>...]
harn servers stop --type preview # preview
harn servers stop --type preview --yes # apply
harn servers stop --pid 12345 # an unregistered server from the list

Named ids stop immediately. A --type selection previews unless --yes. --pid accepts only a pid that the scan currently reports as an unregistered server inside the project.

Stop uses generation-checked signals. Harnery checks the leader’s recorded start token immediately before signaling its process group. It checks again before a direct-PID fallback and before escalating to SIGKILL after five seconds. Each recorded helper PID also needs its own matching start token. An unknown or different generation refuses the stop without signaling that process or deleting its registry record. Arbitrary recorded stop commands are not supported.

JSON stop results include id, stopped, method, and an optional detail. method is signal, not-running, or refused; generation reports same, different, or unknown when a generation check was made. A refusal returns a nonzero exit status. A successful token check narrows the PID-reuse race but cannot remove the interval between the final read and the signal.

Terminal window
harn servers gc # preview
harn servers gc --yes
harn servers gc --idle-hours 4 --owner-stale-hours 48

gc stops a session server only when all of these hold:

  1. It runs on this machine.
  2. It recorded the agent session that started it.
  3. That session has ended, or has not been observed for the stale window (default 24 hours).
  4. The server has had no recorded use for the idle window (default 2 hours).

A server with no recorded agent, or whose agent’s state cannot be read, is kept. Unknown server generations are also kept, and confirmed cleanup uses the same generation checks as stop. Services are always kept. Run it from a scheduler to keep a machine clean.

See ADR 0207 for the shared rule.

Terminal window
harn servers adopt --pid 12345 --type preview --label "Scene preview"
harn servers adopt --all
harn servers adopt --pid 12345 --kind service --no-owner

adopt registers a server that started without registering. It accepts only pids that the scan currently reports as unregistered servers inside the project. It keeps the scan’s original start token and rechecks that witness before writing the registry record. An unknown or different generation refuses adoption. The adopting agent becomes the owner, so gc can stop the server once that agent ends and the server sits idle; --no-owner records no owner, and --kind service keeps it out of cleanup entirely.

.harnery/config.jsonc
"servers": {
"port_ranges": { "preview": [3100, 3199], "review": [8900, 8949] },
"idle_hours": 2,
"owner_stale_hours": 24
}
Terminal window
harn servers port preview # prints the first free port in the range

port skips ports any process listens on and ports a registered server claims. It fails when the type has no range or the range is exhausted. TypeScript callers use allocateServerPort(type), which returns null when no range is configured. idle_hours and owner_stale_hours change the gc defaults; the command-line flags still win.

Terminal window
harn servers logs <id> -n 100
harn servers open <id>

TypeScript hosts import the registry:

import { createServerToucher, serverId, tryRegisterServer, unregisterServer } from "harnery/core/servers";
const id = serverId("preview", sceneDir);
tryRegisterServer({ id, kind: "session", type: "preview", url, port, scope: sceneDir, log });
const touch = createServerToucher(id); // call on every request
process.on("SIGTERM", () => unregisterServer(id, { pid: process.pid }));

The calling agent session is recorded as the owner. tryRegisterServer returns null instead of throwing when no project resolves, so a tool keeps working outside a Harnery project.

Scripts in other languages use the CLI:

Terminal window
harn servers register --type review --port 8900 --pid $PID --url http://localhost:8900/ --scope ./run
harn servers unregister review-run-1a2b3c --pid $PID

register prints the record as JSON. Without --pid it records the calling process’s parent.