Skip to content

Run it as a service

npx local-review runs until you close the terminal. To have the UI always there on http://localhost:5622, install Local Review and make it a service:

Terminal window
npm i -g local-review
local-review service install # run it at every login
$ local-review service install
Installed Local Review as a LaunchAgent: ~/Library/LaunchAgents/local-review.server.plist
It's running: http://localhost:5622
It starts at login and is restarted if it exits.
Data: ~/.local-review. Logs: ~/.local-review/logs/server.out.log and server.err.log.

It registers Local Review with your OS’s own service manager and starts it:

  • macOS: a LaunchAgent, ~/Library/LaunchAgents/local-review.server.plist, loaded now and at every login, and restarted if it exits.
  • Linux: a systemd user unit, ~/.config/systemd/user/local-review-server.service (under $XDG_CONFIG_HOME if that’s set), enabled and started, and restarted if it exits. A user unit runs while you’re logged in; to keep it running without a login session, and start it at boot, enable lingering once with loginctl enable-linger $USER. Local Review never does that for you.
  • Windows: not supported. local-review service install says so and does nothing. Run local-review in a terminal, or set up a Task Scheduler task yourself.

Both start at login, not at boot (except a Linux unit with lingering on).

Terminal window
local-review service install [--port <n>] [--dry-run] [--from-checkout]
local-review service status [--json]
local-review service restart
local-review service uninstall

Writes the service’s file and starts it, then waits up to 15 seconds for it to answer. If it doesn’t, it says so, prints the last lines of its logs and exits 1.

  • --port <n> picks the port (default 5622). $PORT isn’t used, so a PORT exported for something else can’t end up in the service by accident.
  • It’s safe to run again. It rewrites the file, and restarts the service only if the file changed ((unchanged) otherwise). A reinstall keeps the port, LOCAL_REVIEW_HOME and LOCAL_REVIEW_USER the service was installed with unless you give new ones, and says when it kept one. To drop one, uninstall first.
  • --dry-run prints the file it would write and does nothing else: no file, no directory, nothing started.
  • If the port is already taken by something else (most likely local-review running in a terminal), it stops before writing anything: stop that first, or pick another port.
$ local-review service status
Service: ~/.config/systemd/user/local-review-server.service
Loaded: yes (active (running)), starts at login
Running: yes, pid 48213
UI: http://localhost:5622
Node: ~/.nvm/versions/node/v22.12.0/bin/node
Runs: ~/.nvm/versions/node/v22.12.0/lib/node_modules/local-review/dist/server/cli.js
Data: ~/.local-review
Logs: ~/.local-review/logs/server.out.log and server.err.log

It warns when the Node or the local-review the service runs has gone (after a Node upgrade, say), or when it runs a different local-review from the one you just ran. --json gives the same as JSON, for scripts.

Restarts it, for instance after an upgrade (below).

Stops the service and removes its file. Nothing else: your data in ~/.local-review (and its logs/) stays exactly where it is, as the never-delete guarantee promises. Running it when there’s no service just says so. It works from npx too.

The service runs exactly the local-review you installed it from, with absolute paths, because launchd and systemd start a service with almost no environment of its own:

/usr/local/bin/node /usr/local/lib/node_modules/local-review/dist/server/cli.js serve --quiet --port 5622
  • Node is the one that ran service install, as a real path (never a version manager’s shim).
  • PATH starts with that Node’s own bin directory, then the parts of your shell’s PATH that last (not a version manager’s per-terminal directory such as fnm’s fnm_multishells/…, not npx’s or npm run’s, nothing relative and nothing that doesn’t exist), then the usual system directories. Local Review runs git, so service install refuses if git isn’t on it.
  • HOME is your home directory, so the data home, the logs and git’s user.name (your reviewer id) are the ones you’d get in a terminal.
  • LOCAL_REVIEW_HOME and LOCAL_REVIEW_USER are passed on if they’re set when you install (LOCAL_REVIEW_HOME made absolute). Nothing else from your environment is.
  • It still listens on 127.0.0.1 only, as local-review always does. There’s no option to change that (Localhost security).
  • Its working directory is the data home, and its output is appended to logs/server.out.log and server.err.log there. Local Review writes little (a line when it starts, a note per snapshot, and errors), so they aren’t rotated.

Here is a generated unit, from local-review service install --dry-run on Linux:

# Generated by Local Review (local-review.server). Don't edit it: `local-review service install` rewrites it.
# Node v22.12.0: /usr/local/bin/node
[Unit]
Description=Local Review: the review UI on http://localhost:5622
[Service]
Type=simple
WorkingDirectory=/home/you/.local-review
ExecStart=/usr/local/bin/node /usr/local/lib/node_modules/local-review/dist/server/cli.js serve --quiet --port 5622
Environment="PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
Environment="HOME=/home/you"
StandardOutput=append:/home/you/.local-review/logs/server.out.log
StandardError=append:/home/you/.local-review/logs/server.err.log
Restart=always
RestartSec=10
[Install]
WantedBy=default.target

The macOS plist says the same: RunAtLoad and KeepAlive, with ThrottleInterval 10.

local-review service install refuses to run from npx’s cache, which npm can delete at any time: the service would break at some later login, and it would stay on that one version. It refuses pnpm dlx, bunx and yarn dlx for the same reason, writes nothing and says what to do instead:

$ npx local-review service install
local-review: Local Review is running from npx's cache, which npm can delete at any time, so a service that runs it would break at some later login. Install it first: `npm i -g local-review`, then run `local-review service install` again.

From a clone of the repo, it refuses too: the service would run whatever the checkout last built, and break when it’s cleaned or rebuilt. If that’s what you want (you’re working on Local Review itself), build it and add --from-checkout:

Terminal window
npm run build
npm run lr -- service install --from-checkout

The service runs the files of your global install, so an upgrade only needs a restart:

Terminal window
npm i -g local-review@latest
local-review service restart

If you’ve changed Node since you installed the service (a new version through nvm, fnm or Homebrew), your global packages may have moved with it. Run local-review service install again to point the service at the new ones; it keeps the port and data home. local-review service status warns when this is needed.

  • Tests, on Linux and macOS: the generated plist and unit, read back again; escaping; the PATH; the refusals; install, reinstall, status, restart and uninstall with launchctl and systemctl stood in for. On macOS the plist also passes plutil -lint, and on Linux the unit passes systemd-analyze verify.
  • For real, in CI: the package as you’d install it, against launchd on a GitHub macOS runner and a systemd user instance on a GitHub Ubuntu runner: refused from npx, installed, reinstalled, killed (and restarted by launchd or systemd), restarted, moved to another port, and uninstalled with the data home untouched.
  • Not tested yet: a real logout and login, or a reboot, on either OS.