On this page

Operating and deploying Claude Commander

This page is for whoever moves code between environments and keeps the process alive. Commander's deployment model is a per-project promotion ladder: an ordered list of environments, each a checkout of its own branch, promoted one rung at a time by the server, with each rung recorded.

Tutorial: your first promotion

The tutorial uses the reference layout: a dev worktree on its own branch and ports, promoted to prod.

  1. Register the project with its git path. Commander confines every path it touches to a root allowlist, so the path must sit under a configured project root.
  2. Define the ladder in the project's Deploy tab. Each rung names an environment, a branch, an optional pm2 process, a health URL, a migration command and its database URL environment variable. The ladder is stored as JSON in the project's settings and validated against a schema on save.
  3. Run Verify. The first rung checks that the source tree is clean, the target branch is fast-forwardable, and the version manifest (if present) validates.
  4. Read the plan. Plan lists the commits that will move, capped by PROMOTE_PLAN_MAX, so you know exactly what a promotion carries.
  5. Promote. The server runs the rungs in order: verify, plan, test, push, advance, install, migrate, restart, config, health. Those are the human names; the step log uses the emitted identifiers in the table below, where push is recorded as two steps and the fidelity rung is named config. A failure halts the ladder at that rung and shows the queued commits so nothing is lost. Promote itself is an operator action in the UI; agents prepare, operators press.
  6. Read the record. Every run is stored with per-rung timing, output tails and the identity that triggered it.

How-to

Understand each rung

RungWhat it doesHalts when
verify (plus verify-autoclean when autoclean runs)tree clean (or autoclean of known-generated files, FEAT-046 autoclean), target fast-forwardable, manifest validdirty tree, diverged branch
planlists commits to movenothing to move
testruns the configured test argv in a narrowed environmentnon-zero exit
push-source, push-targetpushes the source branch, then the advanced target branch, to the remote — two recorded steps, not oneremote refuses
advancefast-forwards the target branchnot a fast-forward
installruns the install argv (default npm ci) in the targetnon-zero exit
migrateruns the migrate argv (by default the db:migrate script, which runs node migrate.js) against the database URL named by the stage's migrate.dbUrlEnvledger checksum mismatch, SQL error
restartrestarts the target pm2 processpm2 error
configcompares served configuration against the expected keys, the fidelity check of FEAT-034missing or changed keys
healthpolls the target health URL with retries and delayno 200 within the budget

A rollback records a rewind step, and a promotion that touches a remote records remote. Every timeout and retry count is an environment variable with a named default; none is inline.

Use the operator override

When the tree is dirty for a reason you understand, the Deploy tab offers an override (FEAT-046) if promote_allow_override is on. It needs a reason, is recorded with the run, and still performs a server-side fast-forward safety check so it can never clobber the target. Halts always show the queued commits.

Supervise with pm2

The reference deployment runs commander (prod) and commander-dev under pm2. The heartbeat panel reads pm2 jlist, shows CPU, memory, restart counts and log tails, and offers start, stop and restart per process. Two rules from the pm2 operations runbook: never restart with --update-env from a shell whose environment you do not fully trust, because pm2 stamps that environment into its saved state; and after any change run pm2 save so a host reboot resurrects the right set. Restarting the dev instance drops every live terminal on it; stage and verify changes statically first when people are working.

Supervise with Guardian and systemd

Guardian is the platform supervisor that rebuilds the pm2 set from its own manifest on a recycle. Commander reads Guardian's registry so the monitor lists the same services, and links to Guardian with a key. Services Guardian marks as systemd (a local model server, for example) are handled by health endpoint and systemctl status, with start and stop through a validated sudo argument list (FEAT-049); pm2 verbs are never sent to a systemd unit. If Guardian is not installed, Commander says so rather than guessing.

Deploy the container

The deploy/ directory holds the OCI image build and a tenant compose file. The Dockerfile pins the Claude Code CLI version as a build argument, so an image is reproducible. Tenants get one container, one database and one secrets file. Health is whatever URL the tenant's ladder stage names (see the health rung below).

Sign and check a version manifest

A version manifest describes what a release contains and is validated against deploy/version-manifest.schema.json (JSON Schema 2020-12). Releases are signed with an EdDSA key (RFC 8032, JWS per RFC 7515, key id per RFC 7638) that is separate from the agent-manifest key:

node scripts/sign-version-manifest.js deploy/version-manifest.json
node scripts/release-catalog.mjs

The verify rung refuses a manifest whose signature or schema fails. Versions follow SemVer 2.0; the app version has a single source in package.json and is served to the browser as appVersion on /api/client-config.

Run migrations by hand (dev only)

node migrate.js

Never apply SQL directly, even on dev. A ledger that lists fewer rows than the objects on disk halts the migrate rung on the next promotion, and reconciling it is manual work. The one sanctioned exception is the ledger table's own DDL, which lives in code so that a fresh database can bootstrap.

Watch the host

The heartbeat panel has five tabs: Server (load, memory, disk, CPU steal with warn and error thresholds), Commander (version, uptime, session counts), Platform (Guardian registry), Dependencies (Claude Code CLI version, pinned versions from the governed channel) and Monitor (per-process history). Thresholds and poll intervals are configuration; history is persisted to a file so a restart keeps the graph.

Read usage and cost

The usage panel folds every transcript into per-model token counts and cost using a pricing table with a PRICING_AS_OF date; a stale table is flagged after PRICING_STALE_DAYS. Rate-window meters in the header show the subscription windows. Sub-agent transcripts fold into their parent.

Reference

Ladder rung configuration keys

Each stage is one object in the ladder's ordered stages array. A ladder needs at least two stages, or an empty list to unconfigure it. Stage names must be unique.

Stage keyRequiredMeaning
name, branch, pathyesenvironment identity, its branch, and its checkout. path must be absolute.
pm2noprocess name to restart, as a string
healthnoa single http: or https: URL (RFC 3986), fetched by the health rung
migrate.dbUrlEnvwith migratename of the environment variable holding the target database URL. The URL itself is never stored in the ladder.
migrate.commandnoargv array overriding the default migrate command for this stage
manifestnoversion-manifest path for the verify rung

Retries, delay and timeout for the health rung, and the argv for the install and test rungs, are server-level configuration rather than stage fields — see the table below.

VariableDefaultPurpose
PROMOTE_RUN_TESTSonrun the test rung; PROMOTE_TEST_ARGV defaults to npm,test
PROMOTE_INSTALL_ARGVnpm,ci,--no-audit,--no-fundinstall rung argv, comma-split and run with execFile, never a shell
PROMOTE_MIGRATE_ARGVnpm,run,db:migratemigrate rung argv (the script runs node migrate.js), comma-split, execFile
PROMOTE_HEALTH_RETRIES, _DELAY_MS, _TIMEOUT_MSnamed defaultshealth rung
PROMOTE_ALLOW_OVERRIDEoffexpose the override
PROMOTE_VERIFY_AUTOCLEANoffclean known-generated files at verify
PROMOTE_PLAN_MAXnamed defaultcommits listed by plan
SYSTEMD_CONTROL_ENABLEDoffallow systemd start and stop
TRANSCRIPT_EXCLUDED_DIRSemptypopulations skipped by the scan

Health response

There is no built-in health endpoint. The health rung fetches the URL configured on the target ladder stage, which is validated as http: or https: when the stage is saved, and treats a reachable non-error response as healthy. Timeout, retry count and the delay between retries all route through the configuration chain (PROMOTE_HEALTH_TIMEOUT_MS, PROMOTE_HEALTH_RETRIES, PROMOTE_HEALTH_DELAY_MS), and the rung fails the promotion when every attempt fails. Point a stage's health URL at whatever that environment considers proof of life. Licence-plane errors elsewhere are RFC 9457 problem documents.

Explanation

Why the ladder is server-side. A promotion run from a laptop depends on that laptop's git state, environment and attention. Running it in the server, one rung at a time, with each rung recorded, makes the run reproducible and reviewable, and lets a halt show exactly what is queued.

Why migrate is a rung. Schema and code move together or not at all. The migrate rung runs against the target database named by the rung, reads the URL from the environment rather than storing it, and halts on a checksum mismatch, so a hand-edited migration cannot slip through.

Why the override still checks fast-forward. An override exists for dirty trees, not for rewriting history. The server re-checks that the advance is a fast-forward after the override, so the worst case of a mistaken override is a halted run, not a lost commit.

Why Guardian and pm2 both matter. pm2 keeps a process alive; Guardian keeps the process set correct across a host recycle. The 2026-08-02 incident record in the repository shows what happens when the two disagree about the set: the fix was a pinned working directory in the ecosystem file and a scan exclusion, both now defaults.

You've seen the proof

Ready for a number? Scope your deployment and we'll price it against your own economics.

Get your quote →