Run the worker 24/7
The worker only picks up feedback while it's running. For a real deployment you want it to start on boot, restart if it crashes, and write logs somewhere you can read them. Pick the recipe that matches where you're running it. Each one is copy-paste; swap the paths for yours.
New here? Start with Run the worker to download, configure, and test it once — then come back to make it permanent.
First: the auth caveat
Whatever you run the worker under — a service, a container, a VM — it must inherit a machine that is already authenticated with your agent. For Claude that means you ran claude setup-token as the same user the service runs as (interactive claude /login does not persist a token a headless service can use). The worker probes this at startup and fails fast if it can't reach your agent, so if a service “starts but does nothing,” check its logs for an auth error first. See Claude auth: subscription or API key.
macOS — always-on desktop (launchd)
For a Mac that stays on, a LaunchAgent is the native way to start the worker at login and keep it alive. Write ~/Library/LaunchAgents/app.fdbck.worker.plist:
<?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>app.fdbck.worker</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/node</string>
<string>/Users/you/fdbck/fdbck-worker.mjs</string>
</array>
<key>WorkingDirectory</key> <string>/Users/you/fdbck</string>
<key>RunAtLoad</key> <true/>
<key>KeepAlive</key> <true/>
<key>StandardOutPath</key> <string>/Users/you/fdbck/worker.log</string>
<key>StandardErrorPath</key><string>/Users/you/fdbck/worker.err.log</string>
<!-- so 'node' resolves and setup-token is found -->
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
</dict>
</dict>
</plist>Load it (and reload after edits):
launchctl unload ~/Library/LaunchAgents/app.fdbck.worker.plist 2>/dev/null launchctl load ~/Library/LaunchAgents/app.fdbck.worker.plist tail -f ~/fdbck/worker.log
Find your Node path with which node. A LaunchAgent runs only while you're logged in — if the Mac reboots to the login screen and nobody logs in, it won't start. For a truly headless Mac, enable automatic login, or use a LaunchDaemon in /Library/LaunchDaemons instead.
Linux — systemd (the default choice)
On any modern Linux box — a spare machine, a VPS, a VM — a systemd unit gives you restart-on-crash, start-on-boot, and journalctl logs for free. A user unit is simplest because it inherits your agent auth.
User unit
[Unit] Description=fdbck worker After=network-online.target Wants=network-online.target [Service] ExecStart=/usr/bin/node /home/you/fdbck/fdbck-worker.mjs WorkingDirectory=/home/you/fdbck Restart=always RestartSec=5 # cap memory so a runaway run can't take the box down MemoryMax=2G [Install] WantedBy=default.target
systemctl --user daemon-reload systemctl --user enable --now fdbck-worker loginctl enable-linger "$USER" # keep running after you log out journalctl --user -u fdbck-worker -f # follow the logs
enable-linger is the important line — without it the user unit stops when your login session ends.
System unit (dedicated user)
Prefer a system service? Run it as a dedicated user that has been through claude setup-token. Write /etc/systemd/system/fdbck-worker.service:
[Unit] Description=fdbck worker After=network-online.target Wants=network-online.target [Service] User=fdbck Group=fdbck ExecStart=/usr/bin/node /opt/fdbck/fdbck-worker.mjs WorkingDirectory=/opt/fdbck Restart=always RestartSec=5 MemoryMax=2G [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload sudo systemctl enable --now fdbck-worker sudo journalctl -u fdbck-worker -f
Docker / Compose
A container is a clean way to pin Node and get restart-on-crash. The one wrinkle is auth: the container needs your agent credentials, so mount them in (don't bake them into an image). A restart: unless-stopped policy keeps it up across reboots and crashes.
services:
fdbck-worker:
image: node:22-slim
working_dir: /app
command: node fdbck-worker.mjs
restart: unless-stopped
mem_limit: 2g
environment:
# Subscription auth: run `claude setup-token` and export this in the
# shell that runs compose. (API-key mode: use ANTHROPIC_API_KEY instead.)
- CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN}
volumes:
- ./fdbck-worker.mjs:/app/fdbck-worker.mjs:ro
- ./worker.json:/root/.fdbck/worker.json:ro # worker reads ~/.fdbck/worker.json
- ./repo:/app/repo # a checkout the worker can push from
- ~/.gitconfig:/root/.gitconfig:rodocker compose up -d docker compose logs -f fdbck-worker
Auth goes in as an environment variable, not a mounted home directory. For subscription mode, run claude setup-token (an interactive claude /login does not persist a headless-usable token) and export CLAUDE_CODE_OAUTH_TOKEN in the shell that runs docker compose up. For API-key mode, pass ANTHROPIC_API_KEY the same way. The worker's config file is mounted to ~/.fdbck/worker.json — the only path it reads it from.
Home lab — Proxmox LXC or VM
On Proxmox (or any hypervisor), the worker is happiest in a small LXC container or VM that's always on. There's nothing fdbck-specific about the virtualization — inside the guest you install Node, authenticate your agent, and use the systemd recipe above.
A minimal Debian/Ubuntu LXC, from scratch:
# inside the container, as your user curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt-get install -y nodejs git git clone https://github.com/you/your-repo.git ~/fdbck/repo curl -fsSL https://fdbck.app/fdbck-worker.mjs -o ~/fdbck/fdbck-worker.mjs # authenticate your agent as THIS user, then use the systemd user unit above claude setup-token
Give the LXC/VM enough RAM for a build to run (2–4 GB is comfortable) and set it to start on boot in the Proxmox options. An unprivileged LXC is fine — the worker needs no special capabilities.
Cloud VPS (DigitalOcean, Hetzner, EC2, Fly…)
A $5 VPS is plenty. Provision a small Linux box, install Node + git, clone the repo the worker will push from, authenticate your agent as the user the service runs as, then use the systemd recipe above — that's the durable answer on a VPS.
Two lighter options when you just want it up quickly:
tmux / screen — quick and dirty
Good for a first run or a throwaway box; it survives an SSH disconnect but not a reboot, so graduate to systemd once it's working.
tmux new -s fdbck # start a session node fdbck-worker.mjs # run the worker inside it # detach with Ctrl-b then d; reattach later with: tmux attach -t fdbck
PM2 — process manager
If you already run Node apps with PM2, it handles restart, logs, and a boot hook without writing a unit file:
npm i -g pm2 pm2 start fdbck-worker.mjs --name fdbck-worker --max-memory-restart 2G pm2 logs fdbck-worker pm2 startup # prints a command to run so PM2 restarts on boot pm2 save # remember the current process list
Windows — Task Scheduler or NSSM
On an always-on Windows machine, register the worker so it starts at logon and restarts if it exits.
Task Scheduler (built in)
$action = New-ScheduledTaskAction -Execute "node.exe" ` -Argument "C:\fdbck\fdbck-worker.mjs" -WorkingDirectory "C:\fdbck" $trigger = New-ScheduledTaskTrigger -AtLogOn $settings = New-ScheduledTaskSettingsSet ` -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) Register-ScheduledTask -TaskName "fdbck-worker" ` -Action $action -Trigger $trigger -Settings $settings
NSSM — run as a real service
NSSM wraps the worker as a Windows service (starts before logon, auto-restarts):
nssm install fdbck-worker "C:\Program Files\nodejs\node.exe" "C:\fdbck\fdbck-worker.mjs" nssm set fdbck-worker AppDirectory "C:\fdbck" nssm set fdbck-worker AppStdout "C:\fdbck\worker.log" nssm set fdbck-worker AppStderr "C:\fdbck\worker.err.log" nssm start fdbck-worker
Don't want a server at all?
You can skip the always-on machine entirely and run the worker on a schedule in CI — a GitHub Actions cron that drains the queue every few minutes with an API key. That path (and its trade-offs) is covered in Run it in CI on the main worker page.
Whatever you pick, confirm
- Auto-restart — kill the process and watch it come back (
Restart=always,KeepAlive,unless-stopped, PM2). - Starts on boot — reboot the machine and check the worker is back without you touching it.
- Logs — you can read what it's doing (
journalctl,docker compose logs, the log files above). - Auth survives a restart — the service user stays authenticated with your agent after a reboot.
- Memory cap — a ceiling (2 GB is a good default) so one heavy run can't take the box down.
The dashboard shows a live worker status pill, so once it's running you can confirm from anywhere that fdbck can see it.