Operations runbook
Routine procedures for the production instance on M5M36. Diagnosis by symptom is in troubleshooting.md; deployment mechanics in deployment.md; recovery in backup-recovery.md.
Conventions: commands run on the node as the cluster operator account (ssh M5M36), in
~/apps/claude-remote unless stated. mld … commands run from the gateway (or its laptop wrapper).
Nothing here needs sudo.
Reference
| Item | Value |
|---|---|
| Public URL | https://claude.spboucher.ai |
| Local health | http://127.0.0.1:8620/api/v1/health |
| PM2 processes | claude-remote-runtime, claude-remote-server, claude-remote-backup (03:35 nightly) |
| Code | ~/apps/claude-remote (git; source of truth) |
| Data | ~/claude-remote-data (0700) |
| Workspace | ~/workspace |
| Backups | ~/backups/claude-remote/*.tar.gz.enc (14 kept) |
| Backup passphrase | ~/.config/claude-remote/backup.pass |
| tmux socket | tmux -L claude-remote … |
| Database | psql "$DATABASE_URL" after set -a && . ./.env && set +a |
| Manifest and secrets | gateway: ~/dispatch/apps/claude-remote.json |
Daily / weekly checks
| Check | How | Expected |
|---|---|---|
| Service up | curl -fsS https://claude.spboucher.ai/api/v1/health |
{"ok":true,"version":"…"} |
| Processes | pm2 list |
runtime and server online, low restart counts; backup stopped between runs is normal |
| Components | System page in the application | database, runtime, tmux ONLINE; claude ONLINE once authenticated; agentd ONLINE if configured |
| Last backup | ls -lt ~/backups/claude-remote | head -3 |
a file dated last night |
| Disk | System page (disk free) or df -h ~ |
comfortable margin; task logs can reach 256 MiB each |
| Audit trail | System page → audit, or GET /api/v1/system/audit |
no unexpected auth.login failures or unknown client addresses |
| Active browser sessions | Settings → Security | only your devices |
There is no external alerting: these checks are manual.
Start, stop, restart
pm2 restart claude-remote-server # browsers reconnect; sessions and tasks untouched
pm2 restart claude-remote-runtime # attach clients dropped, then re-attached; tmux untouched
pm2 stop claude-remote-server # the public site stops answering (gateway error)
pm2 logs claude-remote-server --lines 100 --nostream- Both can be restarted in one command, as in the update procedure. The server reconnects to the runtime by itself with a 1–10 s backoff, in whatever order they come up.
- Restart the server after every rebuild of the web application (the CSP hashes are computed at start).
- Never run
tmux -L claude-remote kill-serveron production: it ends every session and task process. Sessions becomeRECOVERABLE, running tasksLOST. - From the gateway,
mld healrestarts missing processes and checks the route; it also runs every 5 minutes on its own.
Planned node reboot
- Tell yourself what will be lost: every running process inside sessions and tasks.
- In the application, let running tasks finish or cancel them; note which Claude sessions matter.
- Reboot. The node logs in automatically; PM2 and
mld healbring the processes back. - Check health, then open the Sessions page: former live sessions are
RECOVERABLE. Restart the ones you need (claude --resumefor known conversations) and close the others.
Update the application
Remote-first, on the node — full procedure in deployment.md:
cd ~/apps/claude-remote && git status
# edit
pnpm -r build
set -a && . ./.env && set +a && node apps/server/dist/migrate.js
pm2 restart claude-remote-runtime claude-remote-server
curl -fsS http://127.0.0.1:8620/api/v1/health
git add -A && git commit -m "…" && git push origin mainSessions and tasks from the shell
tmux -L claude-remote list-sessions # cr-<uuid> = terminal sessions, crt-<uuid> = tasks
tmux -L claude-remote capture-pane -p -t '=cr-<uuid>:' | tail -40 # read-only look at a pane
ls ~/claude-remote-data/tasks/<id>/ # command, log, exit, meta.jsonPrefer the application for terminating sessions and cancelling tasks: it records the state transition
and the audit event. A tmux session killed by hand shows up as RECOVERABLE (or the task as LOST).
Do not tmux attach to a cr-* session from an SSH terminal while a browser is using it: the window
follows the most recently used client's size.
Backups
set -a && . ./.env && set +a && deploy/backup.sh # manual backup; prints "backup ok: <file> (<bytes>)"
pm2 logs claude-remote-backup --lines 20 --nostream # result of the nightly runRestore drill and disaster recovery: backup-recovery.md. Recommended monthly: one restore drill into a scratch database. Outstanding: copy archives off the node; store the passphrase elsewhere.
Access management
| Task | How |
|---|---|
| Revoke a browser session | Settings → Security → sessions list (its WebSockets close immediately) |
| Change the password | Settings → Security (revokes all other sessions) |
| Add / remove a passkey, enable / disable TOTP | Settings → Security (the last second factor cannot be removed) |
| Emergency lock-out of everyone | psql "$DATABASE_URL" -c "UPDATE auth_sessions SET revoked_at = now() WHERE revoked_at IS NULL" — open sockets are closed within 60 s; then change the password |
| Take the site off the Internet | from the gateway: mld tunnel rm claude-remote (route removed; the service keeps running locally), or pm2 stop claude-remote-server on the node |
| Lost second factors | break-glass in backup-recovery.md |
Configuration changes
| Change | Where | Then |
|---|---|---|
Any CR_* variable, CR_EXEC_HOSTS, agentd URL |
manifest on the gateway (regenerate with deploy/make-manifest.py, which keeps the secrets, or edit) |
redeploy through mld; verify health |
| Allow a new remote task host | authorise the node's SSH key on that host (owner decision — it grants a shell there), add the alias to CR_EXEC_HOSTS |
redeploy; test with a trivial task |
| Add a workspace root | CR_WORKSPACE_ROOTS (label=/abs/path, labels unique; server and runtime must agree) |
redeploy |
Rotate CR_RUNTIME_TOKEN |
remove it from the manifest's environment and regenerate | redeploy (both processes restart; sessions survive) |
Rotate CR_SECRET_KEY |
invalidates the enrolled TOTP secret | only together with a TOTP re-enrolment (keep a passkey registered first) |
| Claude Code upgrade | npm install -g @anthropic-ai/claude-code on the node |
new sessions use it; check the System page, the MCP page and a hook-driven WAITING_FOR_INPUT, because the application parses CLI output formats observed with 2.1.x |
Data housekeeping
Automatic (daily janitor in the server): lifecycle events (90 days by default, user setting), node metrics (14 days), login attempts (30 days), audit events (400 days), notifications (90 days), expired auth sessions (7 days after expiry).
Manual:
~/claude-remote-data/tasks/<id>/directories are never deleted by the application. Remove old ones by hand when disk space matters (the task row keeps its metadata; its log is then gone).- Archived sessions stay in the database; there is no purge of
terminal_sessionsrows. - Unregistering a project never deletes its files; remove the directory in a shell if wanted.
Incident notes
When something unexpected happens, record: time, what the System page showed, pm2 list, the last 100
lines of both logs (they contain no secrets, cookies or terminal data by construction), and the audit
entries around the time. Add the outcome to implementation-log.md.
Limitations / not verified
- No monitoring or alerting beyond
mld healand the cluster's own node telemetry; a failed backup or a full disk is not signalled. - The planned-reboot procedure has not been executed on M5M36.
mld tunnel rmand the redeploy-after-manifest-change path are cluster-orchestrator operations taken from the cluster documentation; they were not re-run for this document.- Task directory cleanup and session purge are manual; no tooling exists for them.
- Rotation procedures for
CR_RUNTIME_TOKENandCR_SECRET_KEYare derived frommake-manifest.pyand the code; they were not rehearsed.