On this page

Administering Claude Commander

This page is for the person who owns an instance: installs it, decides how people log in, keeps its secrets, and gets it back when something breaks. Commander is one Node.js process (server.js) plus a PostgreSQL schema named commander, supervised by pm2 in the reference deployment or by a container in the Docker deployment.

Tutorial: first install to first login

This walk-through assumes a Linux host with Node.js 20 or newer, PostgreSQL 14 or newer, and the Claude Code CLI on the PATH.

  1. Clone and install. Clone the repository and run npm ci. The lockfile is authoritative; the promotion ladder uses the same command.
  2. Create the database. Create an empty database and a role that owns it. Commander creates the commander schema itself.
  3. Externalise secrets. Copy .env.example and fill it, or, in the reference deployment, keep secrets in a secrets.env file outside the working tree with mode 0600. The process config reads them with a required() helper and refuses to start when a value is still REPLACE_ME. The minimum set is the database URL, the encryption key (32 bytes hex, used for AES-256-GCM at rest per NIST SP 800-38D), the TLS key and certificate paths, and the initial app password.
  4. Apply migrations. Run:
   node migrate.js
   

The ledger table commander.schema_migrations records each file with a SHA-256 checksum. This is the only sanctioned path for schema change; hand-applied SQL causes the ledger to drift and the next promotion to halt.

  1. Start the process. With pm2:
   pm2 start ecosystem.config.js
   pm2 save
   

The HTTP listener defaults to port 3200 and the WebSocket listener (RFC 6455) to 3201, each with its own TLS. Both bind addresses and ports are environment variables.

  1. Check it is up. The root URL serves the application shell once the process is listening. GET /api/auth/check answers without a session and reports the active auth mode, whether SSO is enabled and whether the local password form applies — it is the endpoint the login gate itself uses. GET /api/client-config serves appVersion along with everything the browser renders from. Migration state is read with node migrate.js, which prints the ledger; it is not exposed over HTTP.
  2. Log in. Open the site, enter the app password. The password is stored as an argon2id hash (RFC 9106). A successful login sets the commander_token cookie; automation may send the same value in an x-auth-token header instead.
  3. Rotate the bootstrap password from the profile menu. Rotation revokes every other session for that identity.

How-to

Choose an authentication mode

The setting auth_mode selects how people prove who they are. It is part of the login seam, which means a wrong value can lock everyone out; change it deliberately and keep the lockout runbook open.

ModeWhat happensWhen to use
passwordSingle operator password, argon2id at rest, opaque session tokens hashed with SHA-256 in commander.auth_sessions.Personal and small-team hosts.
oidcLogin redirects to DataShield Auth. Commander discovers the provider (RFC 8414), runs authorization code with PKCE (RFC 6749, RFC 7636, RFC 8252 for the CLI path) or accepts a broker-issued single-use code at /auth/sso/exchange, verifies the token locally against the provider's JWKS (RFC 7517). auth_oidc_flow chooses auto, code or broker. An email or domain allowlist narrows who may enter.Teams and Enterprise with SSO.

Whatever the mode, Commander refuses a WebSocket upgrade from an origin outside WS_ALLOWED_ORIGINS and refuses embedded-scope tokens on the terminal channel.

Handle a step-up challenge

Some settings are protected by a step-up guard. Writing them without proof of the current password returns 428 Precondition Required; a wrong proof returns 403; a malformed request returns 422. The proof travels in the x-current-password header of the same request. The guarded keys are the login-seam keys: app_password, auth_mode, the auth_oidc_* family and mcp_auth_base_url. Nothing in the product, and no agent, should route around a 428; it exists so that a stolen session cookie alone cannot change how people log in.

Recover from a lockout

Follow the login-lockout runbook shipped in the repository (docs/RUNBOOK-LOGIN-LOCKOUT.md). In outline: confirm which instance is locked, read the current auth_mode from the settings table with psql, set a fresh app password through the documented reload window rather than by editing pm2 environment in place, and record the reset. Any password an agent creates during recovery must be stored in the Password Vault in the same step:

node scripts/vault.js set --instance prod --name "Commander PROD login" --username operator --generate

The vault CLI prints a generated secret exactly once.

Keep credentials in the Password Vault

The vault (FEAT-056) is the operator's known place for every credential the instance or its agents create. Entries live in commander.vault_entries; the secret column is an AES-256-GCM blob under the instance key, the metadata columns are plain so the list can be searched. The UI lists entries masked, reveals on demand with an auto-hide timer, and stamps every reveal on the row. The same library serves the CLI:

node scripts/vault.js list --instance dev
node scripts/vault.js get --instance dev --name "<entry>" --reveal
node scripts/vault.js set --instance dev --name "<what it unlocks>" --secret-stdin < secret.txt
node scripts/vault.js delete --instance dev --name "<entry>"

set upserts by name, case-insensitively, so an agent retry is idempotent. Password length and the reveal auto-hide window are live tunables.

Manage third-party integrations

External Integrations (FEAT-051) stores provider credentials for services such as hosting, DNS or model providers in commander.external_integrations, encrypted with the same key. Each provider type is a descriptor that drives the editor form, the masking rules and a health probe that tells you a token is dead before an agent finds out. Import an existing environment file once with scripts/import-secrets.js; after that, edit through the profile menu. A revealed secret is audited but not step-up gated; treat the session cookie as the guard.

Manage Anthropic accounts

Accounts (FEAT-019) hold one or more Anthropic credentials, OAuth or API key. Personas name the account they run on, and the session breadcrumb shows it. OAuth re-authentication opens a relay flow bound to a flow id so an expired or reused code fails loudly rather than as a timeout.

Apply settings and live tunables

The configuration chain has three links and one direction: an environment variable is the declared default, a row in commander.settings overrides it, and /api/client-config serves the browser what it needs. Settings are edited in the Settings panel; the login-seam keys need step-up. The Advanced tab shows the tunables registry (FEAT-055): 29 keys such as terminal size limits, sweep intervals and list caps, each with its source (env, settings or default), validated on write and on read, applied live without a restart. A blank value restores the default.

Back up and restore

scripts/backup-dbs.sh dumps each instance database on a schedule you set. The dump contains the ciphertext of vault and integration secrets; the encryption key is not in the database, so keep the key file in your secrets store with the same retention. To move an instance, restore the dump, install the same key, run node migrate.js (a no-op when the ledger matches) and start the process. scripts/rotate-encryption-key.js re-encrypts every secret column under a new key in one transaction.

The same script also bundles the project git repositories, so a restore brings back both the database state and the working repos. Destination root, the space-separated database list, the projects directory and a retention window in days are all environment variables with named defaults, and the script is idempotent — safe to run repeatedly from cron or a timer.

Turn on entitlement enforcement

Licences are Ed25519-signed JWS documents verified by lib/entitlements.js. Features map to tier keys; unknown keys are refused. In v3.0.15 enforcement is off by default: gated routes answer, and the header shows the tier. A 72-hour grace period covers an expired licence, and a clock-rollback high-water mark refuses a host whose clock went backwards. The Lighthouse panel shows the licence plane state (contract 1.0.2; errors are RFC 9457 problem documents).

Exclude a transcript population

Commander scans ~/.claude/projects for transcripts. One runaway population can stall the scan. TRANSCRIPT_EXCLUDED_DIRS names directories to skip; the flood alert (threshold and window are configurable) warns before that point. Archive sweeps move cold transcripts to an archive directory; the trash sweep purges deleted sessions after their retention.

Reference

Ports and processes

ItemDefaultSource
HTTP3200 (prod), 3210 (dev)COMMANDER_PORT
WebSocket3201 (prod), 3211 (dev)COMMANDER_WS_PORT
Bindall interfacesCOMMANDER_BIND, COMMANDER_WS_BIND
Processpm2 commander / commander-devecosystem.config.js
Databasecommander schemaDATABASE_URL

Authentication endpoints

RouteAuthPurpose
POST /api/authrate-limitedpassword login, sets cookie
GET /api/auth/oidc/start, /callbackrate-limitedOIDC code flow
POST /auth/sso/exchangebroker codesingle-use broker exchange
POST /api/auth/logoutsessionrevoke current session
PATCH /api/settings with app_passwordsession + step-uprotate the password; the server then revokes every other session
GET /api/auth/checknoneauth mode, SSO enabled, whether the password form applies

Settings families

FamilyExamplesStep-up
Login seamapp_password, auth_mode, auth_oidc_*, mcp_auth_base_urlyes
Terminalterm_default_cols, term_max_rows, term_block_suspendno
Sessionssession_park_enabled, session_close_default, archive park minutesno
UIui_native_context_menu, header_usage_meters, docs_uino
Deploypromote_allow_override, ladder JSON per projectno
Vaultvault_password_length, vault_reveal_autohide_msno

The full key list (204 settings keys, 63 client-config keys, 29 live tunables and the environment inventory) is in the reference.

Where data lives

DataLocationEncrypted
Sessions, projects, personas, promptsPostgreSQL commander.*no
Vault secrets, integration credentials, account keysPostgreSQL, secret columnsAES-256-GCM
Transcripts~/.claude/projects on the host, indexed in DBno
Screenshots, uploads, artifactsconfigured directories, content-addressed for artifactsno
Session tokensDB as SHA-256 hash onlyone-way

Explanation

Why one configuration chain. A tunable that can be set in three places with no order is three bugs waiting. Commander's rule is that a literal is only allowed as the named default at the top of the chain, the settings table can override it, and the browser hardcodes nothing because it renders from /api/client-config. That is what lets the same build serve a personal host and a multi-tenant container.

Why the login seam is guarded twice. A session cookie proves someone logged in once. Changing how anyone logs in deserves fresh proof, so the step-up guard asks for the current password on those keys, and the runbook keeps the change on an operator's hand rather than an agent's.

Why secrets live outside the tree. A worktree is copied, promoted and sometimes committed. A 0600 file outside it cannot leak through any of those paths, and PTY_ENV_BLOCKLIST keeps the same values out of the environment that agent terminals inherit.

Why the vault exists. During one lockout an agent reset the production password and nobody could find it afterwards. The vault is the answer: one library, one table, one CLI, and a rule that every generated credential lands there in the same step.

You've seen the proof

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

Get your quote →