On this page
Claude Commander reference
This page states facts. Nothing here teaches, argues or recommends; the reasons live on the admin, DevOps, developer and application pages. Every key, route, frame type and status code below was read out of the tree at d2907aa, version 3.0.15. Where a count is given it is the count found at that commit, not a target.
Two conventions run through everything on this page:
- The configuration chain has one direction. An environment variable is the declared default, a row in
commander.settingsoverrides it, and/api/client-configserves the browser what the browser needs. The literal default lives once, in code, at the top of the chain. - The server is authoritative. A client renders from served configuration. When a value appears both in the settings catalogue and in the served client configuration, the served name is a projection of the setting, not a second source.
Machine-readable descriptions
| Artefact | Standard | Status | Contents |
|---|---|---|---|
/v2-assets/commander-documentation/openapi.json | OpenAPI 3.1.0 | shipped | 160 paths, 192 operations, generated from the route inventory |
./asyncapi.yaml | AsyncAPI 3.0 | P1 — not yet written | the WebSocket PTY channel; until it exists, the frame protocol below is the description |
openapi.yaml is generated, not hand-maintained: scripts/route-inventory.mjs parses the route table out of server.js and lib/*.js, and scripts/build-openapi.mjs merges it with the hand enrichments in openapi.overrides.json (keyed by operationId). Regenerate with:
node scripts/build-openapi.mjs
The generator emits three vendor extensions and refuses to write a document whose $refs do not resolve:
| Extension | Meaning |
|---|---|
x-source | file:line of the route handler that serves the operation |
x-entitlement | { anyOf: [...] } — the feature keys that satisfy the route's requireFeature(...) |
x-operator-hand | the operation touches the login seam or the promotion ladder; it is pressed by an operator, never driven by an agent |
x-rate-limited | a rate limiter is mounted in front of the handler |
Error responses are named components (components/responses/BadRequest, Unauthorized, Forbidden, NotFound, Conflict, UnprocessableContent, StepUpRequired), each carrying the single Error schema. Two security schemes are declared, both the same opaque token: cookieAuth (commander_token cookie) and headerAuth (x-auth-token header). The server URL is a template variable — the document names no instance.
commander.settings key catalogue
commander.settings is a key, value, encrypted, updated_at table. Values are text; the reader coerces. 178 distinct keys are read by the server at this commit, grouped below by the area that reads them. Three sub-classes behave differently from the rest:
- Encrypted at rest (AES-256-GCM via
lib/crypto, NIST SP 800-38D), enumerated bySETTINGS_ENCRYPTED_KEYSinlib/settings-secrets.js:github_token,anthropic_api_key,auth_oidc_client_secret,lighthouse_license_key,guardian_status_api_key. - Masked on read (
GET /api/settingssubstitutes********),SETTINGS_MASKED_KEYSinserver.js: the five above plusapp_passwordandapp_password_hash. APATCHthat echoes the mask back means unchanged and never overwrites the stored secret. - Server-written state, not operator input:
app_password_hash,licensing_hwm,lighthouse_last_result,agent_sync_last_result.
getSettings() caches the whole map for 30 s; PATCH /api/settings invalidates it, so an edit applies within one cache window without a restart.
Authentication and the login seam (25)
Read by lib/auth.js, lib/auth-guard.js, lib/auth-oidc.js, server.js. Every key here is step-up guarded — see errors.
| Key | Controls |
|---|---|
app_password | write-only inbound; hashed to app_password_hash (argon2id) and stored blank |
app_password_hash | the stored argon2id verifier; served masked, never written directly |
auth_mode | local, oidc or hybrid |
auth_oidc_issuer | OIDC issuer; falls back to mcp_auth_base_url |
auth_oidc_client_id, auth_oidc_client_secret | relying-party credentials |
auth_oidc_provider | provider profile name |
auth_oidc_flow | auto, code or broker |
auth_oidc_scopes, auth_oidc_profile_claims | requested scopes; claims copied into the session profile |
auth_oidc_redirect_uri | explicit redirect URI; empty derives it from the request host |
auth_oidc_well_known_path | discovery path (RFC 8414) |
auth_oidc_allowed_algs | accepted JWS algorithms |
auth_oidc_allowed_emails, auth_oidc_allowed_domains, auth_oidc_allowed_subjects | authorisation allowlists; all empty means any active subject of the issuer |
auth_oidc_clock_skew_sec | tolerated clock skew on token validation |
auth_oidc_discovery_ttl_ms, auth_oidc_jwks_ttl_ms, auth_oidc_state_ttl_ms, auth_oidc_http_timeout_ms | discovery/JWKS cache lifetimes, state lifetime, upstream timeout |
auth_rate_max, auth_rate_window_ms | login rate limiter |
auth_session_idle_hours, auth_session_absolute_hours | session idle and absolute lifetimes |
Sessions, transcripts and sweeps (13)
Read by server.js and lib/claude-sessions.js.
| Key | Controls |
|---|---|
session_park_enabled | whether a session can be parked instead of closed |
session_close_default | the default action of the close control |
session_archive_park_idle_minutes | idle minutes before a parked session is archived |
session_recovery_direct_rejoin | interrupted sessions jump straight to the rejoin confirm |
session_active_output_ms | output window that still counts a terminal as active |
session_list_decorate_budget_ms | time the session list spends attaching conversation titles before answering |
session_usage_poll_ms | per-session usage poll cadence served to the client |
transcript_excluded_dirs | comma-separated transcript populations excluded from summarisation |
transcript_flood_threshold, transcript_flood_window_hours | flood-alert threshold and window |
transcript_unknown_alert_pct | warn above this share of unclassifiable transcripts |
archive_sweep_interval_ms | hot→cold roll cadence |
trash_sweep_interval_ms | purge cadence for trashed transcripts past retention |
Terminal and WebSocket (12)
| Key | Controls |
|---|---|
term_default_cols, term_default_rows | PTY geometry before the browser reports its size |
term_max_cols, term_max_rows | clamp applied to a resize frame |
term_scrollback_lines | server-side shadow emulator scrollback |
scroll_persist_chars | characters of scrollback persisted to the session row |
term_block_suspend | swallow the suspend keystroke in the terminal |
term_jump_refresh_ms, term_jump_blank_fraction, term_scroll_dblclick_ms | jump-to-bottom refresh, blank-screen heuristic, double-click window |
osc52_clipboard_enabled | allow OSC 52 clipboard writes from the PTY |
ws_handshake_timeout_ms | client-side WebSocket handshake budget |
Claude Code launch and injection (21)
Read by server.js, lib/cc-config.js, lib/cc-version.js, lib/cc-watch.js.
| Key | Controls |
|---|---|
cc_config | the instance layer of the Claude Code layered config (JSON) |
cc_config_enabled | whether the layered config is resolved and written at all |
cc_version_command, cc_version_ttl_ms | how the installed CLI version is detected, and its cache lifetime |
cc_pretrust_enabled | pre-trust the working directory so the CLI does not ask |
cc_disable_remote_control | set the CLI's remote-control opt-out for launched sessions |
cc_resume_prompt | suppress or native — the CLI's own resume prompt |
cc_resume_threshold_minutes, cc_resume_token_threshold | thresholds handed to the CLI when the prompt is suppressed |
claude_cli_path | path to the Claude Code binary |
claude_active_pattern, claude_idle_pattern | output patterns that classify a session as working or idle |
claude_trash_retention_days | retention before a trashed transcript is purged |
claude_usage_url | external usage help link served to the client |
inject_ready_pattern, inject_ready_timeout_ms | readiness detection before injecting a boot prompt |
inject_settle_ms, inject_shell_delay_ms | settle delays around injection |
inject_dialog_pattern, inject_dialog_affirm_pattern, inject_dialog_max_steps | first-run dialog detection, the affirmative answer, and the step ceiling |
Git panel and git tracking (7)
| Key | Controls |
|---|---|
git_probe_timeout_ms | per-command timeout for quick status/branch probes |
git_details_timeout_ms | timeout for history and diff reads |
git_diff_max_bytes, git_file_max_bytes | truncation caps on a served diff and a served file |
git_file_history_limit | commits listed in per-file history |
commit_meta_max_hashes | hashes resolved in one commit-metadata request |
github_token | GitHub credential (encrypted, masked) |
Deploy, ladder and platform (5)
| Key | Controls |
|---|---|
promote_run_tests | whether the ladder's test rung runs |
deploy_manifest_signing_key | key id used to sign a version manifest |
deploy_badge_poll_ms | deploy-badge poll cadence served to the client |
ecosystem_path | pm2 ecosystem file reported by the platform monitor |
guardian_status_api_key | Guardian link credential (encrypted, masked) |
Every other ladder parameter is per-project ladder JSON, not a settings key — see the DevOps reference.
Heartbeat and host monitoring (7)
| Key | Controls |
|---|---|
heartbeat_poll_interval_ms | monitor poll cadence |
heartbeat_pct_warn, heartbeat_pct_err | load/memory/disk warn and error thresholds |
heartbeat_steal_warn, heartbeat_steal_err | CPU-steal warn and error thresholds |
heartbeat_log_lines, heartbeat_log_lines_max | default and maximum pm2 log lines |
Usage, windows and pricing (13)
Read by lib/usage-stats.js and lib/usage-window.js.
| Key | Controls |
|---|---|
usage_stats_days | default window of the stats scan |
usage_stats_cache_s, usage_stats_resp_cache_max | scan cache lifetime and response-cache size |
usage_stats_fold_max | entries kept in the per-transcript-file fold cache (LRU) |
usage_session_window_ms, usage_weekly_window_ms | the two rate-window meter spans |
usage_window_budgets_json | per-window budgets the meters measure against |
usage_window_warn_pct, usage_window_crit_pct | meter warn and critical thresholds |
usage_stats_poll_ms, usage_meter_poll_ms | client poll cadences, served |
model_pricing_json | operator override of the embedded pricing registry |
pricing_stale_days | age at which the embedded registry is flagged stale |
Artifacts (6)
All six are live tunables; see live tunables for bounds.
| Key | Controls |
|---|---|
artifact_sync_enabled, artifact_sync_interval_ms | the incremental transcript scan and its cadence |
artifact_scan_batch_records, artifact_scan_max_line_bytes | records per extractor batch; the single-line out-of-memory guard |
artifact_list_max | rows the Artifacts panel loads per scope |
artifact_serve_max_bytes | largest local artifact the built-in viewer serves |
Integrations and provider credentials (5)
| Key | Controls |
|---|---|
anthropic_api_key | legacy credential row (encrypted, masked); accounts are the live path |
anthropic_api_key_billing_enabled | whether API-key billing may be used for launches |
openai_api_key, google_api_key | one-time auto-seed sources for provider accounts at first boot |
integration_probe_timeout_ms | how long an integration health probe waits |
Password Vault (2)
| Key | Controls |
|---|---|
vault_password_length | length of a generated password (12–128) |
vault_reveal_autohide_ms | how long a revealed secret stays visible |
MCP broker and consumer (14)
Read by lib/mcp-broker.js and lib/mcp-routes.js.
| Key | Controls |
|---|---|
mcp_broker_mode | off, local or brokered |
mcp_auth_base_url | Auth issuer (also the OIDC issuer fallback — step-up guarded) |
mcp_gateway_base_url | gateway the injected servers point at |
mcp_client_id, mcp_client_auth | broker client identity and client-authentication method |
mcp_instance_id, mcp_app_scope | this instance's identity and requested scope |
mcp_contract_major | accepted contract major version |
mcp_well_known_path, mcp_entitlements_path | discovery and entitlement paths on the broker |
mcp_entitlement_ttl_s, mcp_token_max_ttl_s | entitlement cache lifetime, ceiling on an issued token's lifetime |
mcp_broker_timeout_ms | upstream timeout |
mcp_sender_constraint | sender-constraint requirement on issued tokens |
Licence plane and entitlements (16)
Read by lib/entitlements.js and lib/lighthouse.js.
| Key | Controls |
|---|---|
licensing_enforced | master switch; false (the default) resolves tier unenforced |
license_jwt | the installed Ed25519-signed licence document |
licensing_hwm | clock-rollback high-water mark (server-written) |
ui_feature_overrides | JSON map that force-disables entitled features; narrowing only |
lighthouse_base_url, lighthouse_instance_key, lighthouse_license_key | plane endpoint and credentials (key encrypted, masked) |
lighthouse_contract_major | accepted contract major (1.0.2 at this commit) |
lighthouse_activate_path, lighthouse_renew_path, lighthouse_availability_path | plane paths |
lighthouse_renew_interval_s, lighthouse_renew_ahead_hours | renewal cadence and lead time |
lighthouse_availability_ttl_s, lighthouse_timeout_ms | availability cache lifetime, upstream timeout |
lighthouse_last_result | last plane result (server-written) |
Agent manifest and persona sync (7)
| Key | Controls |
|---|---|
agent_manifest_enabled | serve /.well-known/agents/manifest.jws (default off) |
agent_manifest_namespace, agent_manifest_cache_s | manifest namespace and cache lifetime |
agent_sync_enabled, agent_sync_interval_s, agent_sync_stale_after_s | governed persona sync switch, cadence and staleness horizon (default off) |
agent_sync_last_result | last sync result (server-written) |
Projects and filesystem (5)
| Key | Controls |
|---|---|
default_model | model a launch falls back to |
default_shell | shell the PTY spawns |
default_project_color, project_environments | defaults applied when a project is created |
fs_extra_roots | extra absolute paths added to the filesystem root allowlist |
Screenshots (3)
| Key | Controls |
|---|---|
screenshot_categories | category vocabulary served to the gallery |
screenshot_ai_model, screenshot_ai_max_tokens | model and token ceiling for screenshot description |
UI surfaces and cadences (17)
| Key | Controls |
|---|---|
theme | stored UI theme |
ui_native_context_menu | native right-click policy (FEAT-054) |
quick_actions | JSON descriptor list for the quick-actions rail |
docs_ui, docs_activity_weeks | docs-surface layout JSON; weeks of commit activity summarised |
header_usage_meters_enabled | header rate-window meter tiles |
releases_tab_enabled | Releases tab and its endpoints |
blueprint_wizard_enabled | Blueprint wizard mount |
login_alert_enabled, login_alert_max_age_ms, login_alert_poll_ms | app-wide login-expiry alert |
active_model_poll_ms, active_model_stale_ms | active-model badge poll and staleness |
slug_check_debounce_ms | slug-availability debounce |
screenshots_page_max, prompt_list_limit, prompt_list_limit_max | list page sizes and caps |
/api/client-config
Unauthenticated, read-only, served by server.js. 59 top-level keys at this commit. The browser must render from these and hardcode nothing.
| Key | Shape | Source |
|---|---|---|
appVersion | string | package.json |
wsPort | number | the port the WebSocket listener actually bound |
blueprintWizard | {mount, enabled} | mount constant + blueprint_wizard_enabled |
mcp | {mode, orgEnabled, authBaseUrl} | broker config; authBaseUrl is the public issuer, never a secret |
defaultModel | string or null | default_model |
handshakeTimeoutMs | number | ws_handshake_timeout_ms |
termJumpRefreshMs, termScrollDblclickMs, termJumpBlankFraction, termBlockSuspend | number/number/float/bool | term_* |
deployBadgePollMs, slugCheckDebounceMs | number | same-named settings |
usageMeterPollMs, usageStatsPollMs, sessionUsagePollMs | number | usage poll cadences |
headerUsageMetersEnabled | bool | header_usage_meters_enabled |
osc52ClipboardEnabled | bool | osc52_clipboard_enabled |
claudeUsageUrl | string | claude_usage_url |
activeModelPollMs, activeModelStaleMs | number | active_model_* |
heartbeatPollIntervalMs, heartbeatPctWarn, heartbeatPctErr, heartbeatStealWarn, heartbeatStealErr | number | heartbeat_* |
loginAlertEnabled, loginAlertMaxAgeMs, loginAlertPollMs | bool/number/number | login_alert_* |
integrationsEnabled, vaultEnabled | bool | presence of the backing table |
vaultRevealAutohideMs | number | tunable vault_reveal_autohide_ms |
vaultCategories, integrationCategories, integrationProviderTypes | arrays | lib/vault.js, lib/integration-providers.js vocabularies |
projectsBaseDir | string | served start directory for the file browser |
injectReadyTimeoutMs, injectSettleMs, injectShellDelayMs, injectReadyPattern, injectDialogPattern, injectDialogAffirmPattern, injectDialogMaxSteps | number/string | inject_* |
ccDefaultSettingsPath | string or null | shared --settings fallback path; null means the mitigation is off |
claudeActivePattern, claudeIdlePattern | string | claude_*_pattern |
nativeContextMenu | string | resolved FEAT-054 policy |
sessionArchiveParkIdleMinutes, sessionParkEnabled, sessionCloseDefault, sessionRecoveryDirectRejoin | number/bool/string/bool | session_* |
screenshotCategories | array | screenshot_categories, split on commas |
anthropicApiKeyBillingEnabled | bool | resolved billing policy |
releasesTabEnabled | bool | resolved Releases-tab flag |
entitlements | {enforced, tier, state, features, limits, trialUntil, installedLicense} | resolved entitlements |
licensingCatalog | {features, tiers, limits, trialTier} | the non-secret tier↔feature vocabulary |
docsUi | object incl. imageExts | docs layout config |
lighthouse | {configured} | whether a licence plane is configured |
quickActions | array of descriptors | quick_actions |
floodPollMs | number | transcript flood re-check cadence |
Nested members raise the total addressable name count past the top-level 59 (blueprintWizard.mount, mcp.mode, entitlements.features, docsUi.imageExts and so on). No key on this route is a secret; integrationsEnabled and vaultEnabled report a table's presence, not its contents.
Live tunables (29)
lib/tunables.js is one registry and one resolver: settings row (valid) → environment variable (valid) → named default. An invalid value warns once and falls through rather than being honoured. GET /api/tunables returns the descriptor plus the current resolution and its provenance (settings, env or default) for the Settings ▸ Advanced renderer; PATCH /api/settings validates a tunable on write, and a blank value restores the default. All 29 are live: true at this commit — a save applies within the 30 s settings-cache window, with no restart.
| Key | Type | Default | Bounds | Group | Controls |
|---|---|---|---|---|---|
artifact_sync_enabled | bool | true | — | artifacts | run the periodic artifact scan |
artifact_sync_interval_ms | int | 600000 | ≥30000 | artifacts | scan cadence |
artifact_scan_batch_records | int | 2000 | 100–100000 | artifacts | records per extractor batch |
artifact_scan_max_line_bytes | int | 8388608 | ≥65536 | artifacts | drop (and log) a longer single line |
artifact_list_max | int | 500 | 20–5000 | artifacts | rows per scope in the panel |
artifact_serve_max_bytes | int | 26214400 | ≥1048576 | artifacts | viewer file cap |
trash_sweep_interval_ms | int | 3600000 | ≥60000 | transcripts | purge cadence |
archive_sweep_interval_ms | int | 3600000 | ≥60000 | transcripts | hot→cold roll cadence |
session_list_decorate_budget_ms | int | 250 | 0–10000 | transcripts | title-decoration budget |
session_active_output_ms | int | 10000 | ≥1000 | transcripts | active-session output window |
transcript_unknown_alert_pct | float | 5 | 0–100 | transcripts | unknown-kind alert threshold |
term_default_cols | int | 120 | 20–1000 | terminal | initial PTY width |
term_default_rows | int | 40 | 5–500 | terminal | initial PTY height |
term_max_cols | int | 500 | 40–5000 | terminal | resize clamp |
term_max_rows | int | 300 | 10–2000 | terminal | resize clamp |
git_probe_timeout_ms | int | 5000 | ≥500 | external | quick git probes |
git_details_timeout_ms | int | 10000 | ≥500 | external | heavy git reads |
integration_probe_timeout_ms | int | 12000 | ≥1000 | external | integration test |
heartbeat_log_lines | int | 100 | 10–5000 | limits | default pm2 log lines |
heartbeat_log_lines_max | int | 500 | 10–20000 | limits | requestable ceiling |
screenshots_page_max | int | 500 | 20–5000 | limits | gallery page cap |
prompt_list_limit | int | 100 | 10–5000 | limits | prompt library page |
prompt_list_limit_max | int | 500 | 10–20000 | limits | requestable ceiling |
docs_activity_weeks | int | 12 | 1–104 | limits | docs activity window |
pricing_stale_days | int | 90 | 1–3650 | limits | pricing staleness horizon |
default_project_color | color | #3b82f6 | #rrggbb | projects | colour for a new project |
project_environments | csv | production,test,dev | non-empty | projects | environments created with a project |
vault_password_length | int | 20 | 12–128 | vault | generated password length |
vault_reveal_autohide_ms | int | 30000 | 3000–600000 | vault | reveal auto-hide |
Each entry also carries env (the variable name, the tunable key upper-cased), unit, label and help; the seven groups are artifacts, transcripts, terminal, external, limits, projects, vault.
Environment inventory
The full environment inventory is generated into tracking/features/FEAT-055.md (one table row per variable, with where it is read and its disposition). It is not repeated here — 248 rows would drift the moment a variable moves. The triage classes are:
| Class | Count | Meaning |
|---|---|---|
| A · exposed | 29 | in the tunables registry, editable live in Settings ▸ Advanced |
| B · settings-backed elsewhere | 14 | already has its own control in Settings outside the Advanced tab |
| C · wave-2 candidate | 99 | read once at boot or module init; exposable through the same registry after the consumer is converted to a use-time read or scheduleLoop, or with live: false |
| D · env-only by design | 106 | deployment shape (bind hosts, paths, CSP, blocklists, argv, identities, protocol URLs) or secrets; these belong to the process config, never to a settings row |
Read the disposition column before adding a variable: a new tunable belongs in the registry, and a new deployment-shape variable belongs in class D with no settings row at all.
WebSocket frame protocol
One channel, path /ws, on its own HTTPS listener (default port 3201, COMMANDER_WS_PORT). Both query parameters are required and must be UUIDs:
wss://<host>:<wsPort>/ws?session=<sessionId>&project=<projectId>
Frames are JSON text (RFC 8259) with a type discriminator. An AsyncAPI 3.0 description is P1; this table is the normative description until it lands.
Client → server
type | Payload | Effect |
|---|---|---|
input | {data: string} | written to the PTY verbatim |
resize | {cols, rows} | clamped by clampDim to the term_max_* bounds; a garbage value falls back to the default rather than throwing |
revive | — | only accepted after a session_dead frame; spawns or attaches once, then ignores repeats |
A frame that is not valid JSON, or whose type is unknown, is dropped. The first malformed frame on a socket is logged and further ones are suppressed, so a flood cannot become a log flood.
Server → client
type | Payload | When |
|---|---|---|
connected | {sessionId, project, reconnected, cols, rows, replay} | on attach; replay is the serialised current screen, never the raw byte history |
output | {data: string} | PTY output, fanned out to every subscriber |
model | {sessionId, model, …, scope: 'session'} | debounced push when the acting model changes in the transcript tail |
cc-watch | {sessionId, watch, extract} | a registered output watch matched |
cc-credit-prompt | {sessionId} | the CLI's credit prompt was detected |
exit | {code} | the PTY exited |
session_dead | {sessionStatus, scrollback} | the session ran before but no PTY is live; nothing spawns until a revive arrives |
error | {message} | refusal or failure; the socket is usually closed straight after |
Transport hardening
Implemented in lib/ws-hardening.js (pure functions) and wired in server.js:
| Control | Behaviour |
|---|---|
| Origin allowlist | RFC 6454 Origin checked against the connected host, plus WS_ALLOWED_ORIGINS (full origins or bare hostnames). No Origin header is treated as a non-browser client and allowed — the session token still gates it. An unparseable Origin or Host fails closed. RFC 6455 §10.2 leaves this policy to the server. |
| Authentication | the same opaque session token as /api, from the commander_token cookie. A password is never accepted here. |
| Embedded scope refused | a session whose profile mode is embedded is refused with error and closed — the terminal is full shell access and is not in the embedded contract |
| Session/project binding | the session row must belong to the project named in the query, or the socket is refused |
maxPayload | WS_MAX_PAYLOAD_BYTES, default 1048576 |
| Keepalive | server ping every WS_KEEPALIVE_MS (default 30000); a socket that misses a pong is terminated. 0 disables it for tests. |
| Backpressure | a subscriber more than WS_MAX_BUFFERED_BYTES behind is terminated as a slow consumer |
| Containment | any rejection inside the handshake is contained to that one socket; it can never take the process (and every other terminal) down |
Error and status conventions
JSON bodies (RFC 8259) on every error, HTTP semantics per RFC 9110.
| Status | Body | Raised by |
|---|---|---|
| 400 | {error} | shape and range validation, including an invalid tunable write ({error, key}) |
| 401 | {error} | no live session on an authenticated route |
| 403 | {error, feature, tier, upgrade} | requireFeature(...) when no listed feature is entitled |
| 403 | {error, path, hint} | a path outside the filesystem root allowlist. The hint names the two remedies: register the location as a project, or extend FS_EXTRA_ROOTS / settings.fs_extra_roots |
| 403 | {error, guardedKeys} | step-up proof supplied but wrong, or no step-up path exists |
| 409 | {error} | name clash on a unique column (integration, vault entry), an existing working_dir, a stale OAuth flow, restart-cc with no live process, or a systemd verb Commander will not run for you (which also carries {needsManualStart, manager, name}) |
| 409 | {error, ...} | optimistic-concurrency skew on an If-Match-style write (RFC 7232 semantics; personas and per-account CC config) |
| 413 | {error} | a cap exceeded — prompt title, body, category, tag count and tag length each have their own |
| 422 | {error, guardedKeys} | a coherent-state refusal: switching auth_mode to oidc with no issuer would strand the login path |
| 428 | {error, guardedKeys} | step-up required (RFC 6585). Resend the identical request with the x-current-password header. Guarded keys are the enumerated floor app_password, auth_mode, mcp_auth_base_url, plus everything matching the auth_oidc_ prefix, plus anything AUTH_CRITICAL_KEYS_EXTRA adds. The floor and the prefix are not shrinkable from the surface they guard. Step-up follows NIST SP 800-63B §5 re-authentication. |
| 500 | {error} | handler failure. One deliberate case: writing a credential-class setting with no COMMANDER_ENCRYPTION_KEY configured is refused rather than persisted in plaintext. |
Two conventions sit outside the table. Licence-plane errors are RFC 9457 application/problem+json documents, produced and parsed by lib/lighthouse.js (with tolerance for Lighthouse's pre-contract error shape). And a guarded settings write is always logged — refused or allowed, with the key list — because a change to the login seam must never be silent.
Entitlement keys
lib/entitlements.js declares the closed vocabulary. requireFeature(f) throws at boot for an unknown key, so a typo fails the process rather than a request. 19 feature keys, carried by 39 routes (36 through requireFeature, which is also the count the generated OpenAPI document marks with x-entitlement; plus 3 through the usage plan gate):
| Feature key | Routes carrying it |
|---|---|
tasks-notes | 13 — the git-tracking notes and tasks routes plus the DB task routes |
session-manager ∪ session-rejoin (any-of) | 8 — /api/artifacts*, GET /api/claude-sessions, unarchive, restart-cc, GET /api/transcripts/stats |
deploy-ladder | 3 — promote, rollback, rollback-candidates |
releases-tab | 3 — the release-notes routes |
session-manager | 3 — brief, trash, GET /api/transcripts/export |
token-analytics | 3 — /api/usage/stats, /api/usage/stats/progress, /api/usage/window (plan gate) |
git-history | 2 — git-file-history, git-file-at-ref |
session-rejoin | 1 — archived sessions for a project |
session-rejoin ∪ session-manager | 1 — GET /api/claude-sessions/:sid/exists |
transcript-search ∪ session-manager | 1 — GET /api/claude-sessions/:sid/prompts |
docs-visuals | 1 — GET /api/files/activity |
custom-agents, doc-viewer-plus, git-file-panel, llm-runtime-controls, quick-actions-toolbar, multi-account, multi-project, project-nesting, shell-sessions, usage-analytics | no route gate — UI-surface keys, or enforced as a numeric limit |
Numeric limits are separate from features: checkLimit() reads limits['projects:max'] and limits['accounts:max'], both capped at 1 for the community tier and absent (unlimited) above it.
Enforcement is off by default in v3.0.15. licensing_enforced resolves from LICENSING_ENFORCED then the settings row, defaulting to false; unenforced resolves tier unenforced with every feature on, so a gated route answers and the client still renders the installed licence's tier and expiry for display. Turning enforcement on switches the resolver to the licence document: an unlicensed install falls to community, TRIAL_TIER (team) covers a trial, and ui_feature_overrides can only narrow what a licence grants — never widen it.
Related
- Admin — which settings to change, and the step-up and lockout procedures.
- DevOps — ladder rung configuration and the deploy environment variables.
- Developer — how to add a setting, a tunable, a migration or a route.
- Application — personas, layered Claude Code config, sessions and artifacts.
/v2-assets/commander-documentation/openapi.json— the generated OpenAPI 3.1.0 document.- Best practices · Agents
You've seen the proof
Ready for a number? Scope your deployment and we'll price it against your own economics.
Get your quote →