harn servers
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
gconce the agent that started it is gone and nobody has used it for the idle window.
harn serversharn servers list --jsonharn servers list --no-scanThe 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.
harn servers stop <id> [<id>...]harn servers stop --type preview # previewharn servers stop --type preview --yes # applyharn servers stop --pid 12345 # an unregistered server from the listNamed 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.
Clean up
Section titled “Clean up”harn servers gc # previewharn servers gc --yesharn servers gc --idle-hours 4 --owner-stale-hours 48gc stops a session server only when all of these hold:
- It runs on this machine.
- It recorded the agent session that started it.
- That session has ended, or has not been observed for the stale window (default 24 hours).
- 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.
harn servers adopt --pid 12345 --type preview --label "Scene preview"harn servers adopt --allharn servers adopt --pid 12345 --kind service --no-owneradopt 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.
Port ranges
Section titled “Port ranges”"servers": { "port_ranges": { "preview": [3100, 3199], "review": [8900, 8949] }, "idle_hours": 2, "owner_stale_hours": 24}harn servers port preview # prints the first free port in the rangeport 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.
Logs and URLs
Section titled “Logs and URLs”harn servers logs <id> -n 100harn servers open <id>Registering a server
Section titled “Registering a server”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 requestprocess.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:
harn servers register --type review --port 8900 --pid $PID --url http://localhost:8900/ --scope ./runharn servers unregister review-run-1a2b3c --pid $PIDregister prints the record as JSON. Without --pid it records the
calling process’s parent.