Run the worker as a service
pidgen setup starts the worker in the background, and it keeps running until you stop it,
log out of a machine that ends your processes, or restart the machine. After a restart you
start it again with the Pidgen CLI (see Workers).
A worker that should come back by itself after a restart or a crash belongs under the machine's service manager: systemd on Linux, launchd on macOS. This page shows how to set that up for the worker the Pidgen CLI installed, and how to run a worker in a container instead.
Before you start
Section titled “Before you start”Stop the worker the CLI started. The worker holds a lease on the machine, and a second copy started by the service manager would only wait for that lease while the first copy keeps running outside the service manager's control. Stop it with the Pidgen CLI (Workers), then check that nothing is left:
pgrep -af pidgen-workerIt should print nothing.
Find the worker's program and the tools it needs. A service manager starts programs with
a short PATH, so the service file names the worker by its absolute path and sets a PATH
that reaches Git, Node.js and your coding agents. In your usual shell:
command -v pidgen-workercommand -v git node claude codex piNote each directory these print. Leave out any coding agent you do not use.
Check the machine. pidgen-worker doctor should pass before the service manager starts the
worker; see Logs and diagnostics.
The worker is started with no arguments: pidgen-worker on its own is the long-running
process, and it reads its credential and settings from its home (~/.pidgen unless your
setup chose another directory). It takes no other arguments, so the service file passes none.
Linux: a systemd user service
Section titled “Linux: a systemd user service”A user service runs as you, with your home, your coding-agent logins and the worker's
~/.pidgen, exactly as the worker the CLI started did. You need no root access.
Create ~/.config/systemd/user/pidgen-worker.service, replacing the ExecStart= path with
what command -v pidgen-worker printed and adding the directories you noted to PATH
(%h is your home directory):
[Unit]Description=Pidgen workerDocumentation=https://docs.pidgen.ai/worker/run-as-a-service/
[Service]Type=simpleEnvironment=PIDGEN_HOME=%h/.pidgenEnvironment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/binExecStart=%h/.local/bin/pidgen-workerRestart=on-failureRestartSec=5sKillSignal=SIGTERMKillMode=mixedTimeoutStopSec=120s
[Install]WantedBy=default.targetWhat the settings are for:
| Setting | Why |
|---|---|
Environment=PIDGEN_HOME= |
The worker's home. Keep it the same as the one your setup used. |
Restart=on-failure |
A crash, or a start that fails because the network is not up yet, is retried every 5 seconds. A worker that stopped cleanly stays stopped: one you stopped on purpose, and one whose credential was revoked (see Remove a worker). |
KillMode=mixed, KillSignal=SIGTERM |
A stop sends SIGTERM to the manager alone, which runs the worker's planned stop: it stops taking work, interrupts running steps and hands them back to Pidgen so another worker can pick them up, then exits. |
TimeoutStopSec=120s |
The planned stop takes up to two minutes. Only after that does systemd kill what is left. |
Load the service, start it, and have it start at boot:
systemctl --user daemon-reloadsystemctl --user enable --now pidgen-workerA user service runs only while you are logged in, unless lingering is on. Turn it on so the worker starts at boot and keeps running after you log out:
loginctl enable-linger "$USER"Check it and read its log:
systemctl --user status pidgen-workerjournalctl --user -u pidgen-workerThe prompt, script and workspace workers it starts still write their own logs under
~/.pidgen/logs/; see Logs and diagnostics.
Stopping and restarting
Section titled “Stopping and restarting”While systemd runs the worker, stop and start it with systemd, so the service always knows whether the worker is meant to be running:
systemctl --user stop pidgen-workersystemctl --user restart pidgen-workerBoth run the planned stop and can take up to two minutes. To stop the worker and keep it from starting at the next boot:
systemctl --user disable --now pidgen-workerAfter you change the service file, run systemctl --user daemon-reload and restart the
service. After an update of the worker (see Workers),
restart the service so the new version runs.
macOS: a launchd agent
Section titled “macOS: a launchd agent”On a Mac, launchd runs the worker as a launch agent: a property list in
~/Library/LaunchAgents that starts the worker as you when you log in, and starts it again
if it crashes.
Create ~/Library/LaunchAgents/ai.pidgen.worker.plist. Replace you with your user name,
the first ProgramArguments entry with what command -v pidgen-worker printed, and add the
directories you noted to PATH (/opt/homebrew/bin is Homebrew's on Apple Silicon,
/usr/local/bin on Intel):
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <key>Label</key> <string>ai.pidgen.worker</string> <key>ProgramArguments</key> <array> <string>/Users/you/.local/bin/pidgen-worker</string> </array> <key>EnvironmentVariables</key> <dict> <key>PIDGEN_HOME</key> <string>/Users/you/.pidgen</string> <key>PATH</key> <string>/Users/you/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string> </dict> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict> <key>ExitTimeOut</key> <integer>120</integer> <key>StandardOutPath</key> <string>/Users/you/.pidgen/logs/launchd.log</string> <key>StandardErrorPath</key> <string>/Users/you/.pidgen/logs/launchd.log</string></dict></plist>What the keys are for:
| Key | Why |
|---|---|
ProgramArguments |
The worker's absolute path and nothing else: the worker takes no arguments. |
EnvironmentVariables |
The worker's home, and a PATH that reaches Git, Node.js and your coding agents. launchd does not read your shell's profile. |
RunAtLoad |
Start the worker when the agent is loaded, which is at every login. |
KeepAlive with SuccessfulExit false |
Start the worker again when it crashes, but not when it stopped cleanly, for example because its credential was revoked. |
ExitTimeOut |
launchd stops the worker with SIGTERM, which runs its planned stop; this gives that stop its two minutes before launchd kills what is left. |
StandardOutPath, StandardErrorPath |
Where the manager's own output goes. The other workers keep their logs under ~/.pidgen/logs/. |
Keep the home path short: ~/.pidgen is fine, a deeply nested directory is not, because macOS
limits the length of the socket path the worker creates in it.
Check the file, load the agent, and see that it runs:
plutil -lint ~/Library/LaunchAgents/ai.pidgen.worker.plistlaunchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.pidgen.worker.plistlaunchctl print gui/$(id -u)/ai.pidgen.workertail -f ~/.pidgen/logs/launchd.loglaunchctl print shows state = running and the worker's process id.
A launch agent runs while you are logged in. For a Mac that should take work after a restart without anyone logging in, turn on automatic login for your user in System Settings.
Stopping and restarting
Section titled “Stopping and restarting”Restart the worker, running the planned stop first:
launchctl kickstart -k gui/$(id -u)/ai.pidgen.workerStop it and unload the agent:
launchctl bootout gui/$(id -u)/ai.pidgen.workerbootout runs the planned stop and can take up to two minutes. The agent is loaded again at
your next login while the file stays in ~/Library/LaunchAgents; to stop the worker for good,
bootout it and then move the file out of that folder. After you change the file, bootout
the agent and bootstrap it again.
In a container
Section titled “In a container”The worker can also run in a container. Pidgen does not publish a worker image yet
(planned). If you build your own on the published pidgen-worker package, these are what
matter:
-
Run the worker itself, in the foreground. The container's command is
pidgen-workerwith no arguments. Run it under a small init (docker run --init, ortinias the entrypoint) so that signals reach it and finished processes are reaped. Run it as an unprivileged user, not as root. -
Put the home on a volume. Set
PIDGEN_HOMEto a directory on a volume, so the credential, the clones and the logs survive an upgrade of the image. Putworker.env,config/node.yamlandconfig/worker-policy.yamlfrom a worker you set up into it before the first start, owned by the container's user and readable only by it, and stop the worker you copied them from: one credential is one worker, and two copies would contend for the same lease. -
Give the stop two minutes. The container's stop sends
SIGTERM, which runs the planned stop. Docker's default of 10 seconds would kill the worker halfway through and leave its steps stranded until their leases run out. Start the container withdocker run --stop-timeout 120, or under Compose:services:pidgen-worker:stop_signal: SIGTERMstop_grace_period: 120srestart: on-failure -
Let the container restart on failure.
docker run --restart on-failure(Compose:restart: on-failure) brings back a worker that crashed, and leaves one whose credential was revoked stopped. -
Put the tools and the logins in the image. The image needs Git, Node.js and the coding agents on its
PATH, and the agents need their logins inside the container: log them in there, or mount their login directories into it. A login on the host machine is not seen from inside. Runpidgen-worker doctorin the container to check.
What a hard kill skips
Section titled “What a hard kill skips”kill -9, systemctl --user kill, docker kill, a power cut or a sudden sleep skip the
planned stop. Nothing hands the running steps back, so they stay assigned to this worker
until their leases run out, and only then does Pidgen give them to another worker. A run tied
to this machine waits until the worker is back. Stop the worker with its service manager
instead, and give it its two minutes. If a run is still stuck afterwards, see
A run is stranded on a worker that is gone.