Skip to content

Keeping Paddock running on your laptop

npx @edspencer/paddock is a terminal tab. Close the tab, Paddock stops. That is right for trying it, and wrong the moment it becomes the thing you use every day — a chat you want to come back to tomorrow, a schedule you want to fire while you are in a meeting.

Terminal window
npm i -g @edspencer/paddock
paddock service install

That registers Paddock as a per-user background service — a launchd LaunchAgent on macOS, a systemd --user unit on Linux — starts it immediately, and starts it again each time you log in.

paddock service install register it and start it now
paddock service uninstall stop it and remove the unit
paddock service status is it registered, is it running, where are the logs

This is the one thing to know before you install it. Paddock starts when you log in, not when the machine boots. Restart your Mac and walk away, and Paddock is not running until somebody logs in.

That is not an oversight, and it is not fixable by switching to a system daemon. It falls out of how your Claude login works:

  • On macOS, your Claude Code login is a Keychain item. The login keychain is unlocked by your account password at login. A LaunchAgent runs inside that session, as you, so it can read the item. A LaunchDaemon starts at boot with no user session — nothing has supplied the password, the keychain is locked, and UserName in the plist does not help because it changes the effective uid rather than the keychain’s unlock state.
  • So on darwin, claude.credentials: host and boot-time start are mutually exclusive.

A boot-time instance is a different thing, and you can have it — but the price is explicit: a system-level unit with claude.credentials: own and a long-lived CLAUDE_CODE_OAUTH_TOKEN sitting in its environment. That is the server story, not this one.

A systemd --user manager starts at your first login and stops at your last logout — so by default the unit dies when you log out, including out of an SSH session. To keep it up:

Terminal window
loginctl enable-linger $USER

paddock service install prints this rather than running it: lingering changes how the machine behaves after you leave it, and needs a privilege the install otherwise never asks for.

Exactly one file, plus a directory for logs. Nothing else on your machine changes.

macOSLinux
unit~/Library/LaunchAgents/net.edspencer.paddock.plist~/.config/systemd/user/paddock.service
logs~/.paddock/service/paddock.log, paddock.error.logjournalctl --user -u paddock -f
working dir~/.paddock/service~/.paddock/service
restarton crash only (KeepAlive: SuccessfulExit=false)on crash only (Restart=on-failure)

Three details in there are deliberate and worth knowing about:

It invokes node by absolute path, with the script path after it — not the paddock bin. The bin is an npm symlink with a #!/usr/bin/env node shebang, and launchd hands a process a stub PATH that a version manager’s node is not on. Exec’ing the bin directly would fail at login with a bare “no such file or directory”.

It sets no data dir. With nothing named, the service and a paddock typed into a terminal both fall through to ~/.paddock — one instance, two ways to reach it. Pass --data-dir at install time only if you actively want the service to be a different instance from your terminal one.

The environment carries PATH and nothing else. A unit file is a readable file in your home directory, and the value it would be tempting to put in one is a credential — which is precisely what the user-agent shape exists to avoid needing.

paddock: `service install` needs an install path that will still be there at your
next login, and this copy is running from npx's cache — a hash-keyed
directory that `npm cache clean` removes.
npm i -g @edspencer/paddock && paddock service install

npx materialises the package under ~/.npm/_npx/<hash>/. Those copies accumulate, and the whole tree is prunable. A unit pointing in there works right up until it doesn’t — at some future login, unattended. Refusing beats writing a unit we know will rot.

Paddock binds loopback with authentication off, and that stays true here. A service is up for as long as you are logged in rather than for as long as a terminal tab — so the window is longer, but it is not wider: any local process that could reach 127.0.0.1:7233 could already read the same Claude login, as the same user. The daemon changes duration, not reach.

The case that is genuinely worth thinking about is a web page in your browser poking at localhost, and that applies equally to an npx run today. If you want a credential on the port anyway, set PADDOCK_AUTH_MODE — see Authentication. The bind-safety guard still refuses to bind a routable interface with auth off, service or not.

launchctl kickstart while logged in is tested on real hardware: a turn completed against a Keychain-only Claude login, with no credential in the process environment and no permission dialog.

Start at login itself — RunAtLoad, after a genuine logout or reboot — has not been tested. If a login-time agent can start before the login keychain is usable, the symptom would be turns failing with Not logged in after a reboot, recovering after a paddock service uninstall && paddock service install. Please report it if you see it: the fix belongs in the generated plist.

Not supported. Windows has no per-user service concept that maps onto either of these without a scheduled task or a service wrapper, and shipping an untested third writer would be worse than saying so. paddock service tells you as much rather than guessing.

Paddock under systemd on a server is a different setup and documented separately — Deploying Paddock and Running Paddock on Proxmox (LXC) Path B. The differences are not cosmetic:

serverlaptop (this page)
scopesystem unit, service useruser agent, runs as you
credentialstoken in the unit’s environmentyour own Claude login
exposurebehind auth at a reverse proxyloopback only
provisioningAnsible, infra-as-codeone command