On this page

Claude Commander operating practice

Every rule below exists because something went wrong once. They are written down in two places — CLAUDE.md (the Engineering Constitution) and docs/governance/CMD-DEV-STD-001.md (the development-governance standard, v1.1) — plus the runbooks each one points at. This page collects them in one list with the incident behind each, because a rule whose reason is unknown is a rule that gets routed around.

The audience is both halves of the pair: the operator who owns the instance, and the agent working inside it. Where a rule says operator-hand, it means an agent prepares the work and leaves it pending; an agent is never its own approver.

How-to

Put every credential an agent creates into the Password Vault

Do this. Any password, token or key an agent generates or resets for the operator goes into the Password Vault in the same step that creates it:

node scripts/vault.js set --instance dev --name "<what it unlocks>" --generate
node scripts/vault.js set --instance dev --name "<what it unlocks>" --secret-stdin < secret.txt

--instance dev|prod is required and selects which instance's database and encryption key to use, read from COMMANDER_DB_URL_<INSTANCE> and COMMANDER_ENCRYPTION_KEY_<INSTANCE> in the environment or the out-of-worktree secrets.env (--file points at a different one). Supply the secret exactly one way: --generate (length from the vault_password_length tunable, or --length N), --secret-stdin, or --secret-env VAR. Metadata flags are --username, --url, --category login|api_key|token|database|ssh|other, --notes, --tags and --by agent|operator. The other verbs are list, get --name … [--reveal], delete --name …, and generate (prints one and stores nothing). set upserts by name, case-insensitively, so a retry is idempotent rather than a duplicate row.

What breaks otherwise. During one lockout an agent reset the production password, announced it in chat, and the chat scrolled away; nobody could find the credential afterwards. A secret that exists only in a transcript is lost, and a secret committed to git is permanently compromised. The vault is one table (commander.vault_entries), one encryption path (AES-256-GCM, NIST SP 800-38D), one library shared by the routes and the CLI, and one known place for the operator to look.

Where it is written. CLAUDE.md, "Credentials an agent creates". Secret material must never appear in a tracked file — docs/SECURITY-P0-ROTATION.md.

Do not restart commander-dev while someone is using it

Do this. Treat a dev restart as a scheduled, announced act. Static assets (public/index.html, public/vendor/*) need only a browser reload, so most front-end work needs no restart at all. For server.js or lib/ changes while the operator is connected: stage the change, verify it statically (transpile-verify the SPA block, run the suite, read the diff) and restart in an agreed window. When you do restart, pm2 restart commander-dev — never with --update-env.

What breaks otherwise. Restarting drops every live WebSocket terminal on the instance, including the operator's own session and any agent working in it. And --update-env stamps the invoking shell's environment into pm2's saved state: the 2026-08-16 env-clobber incident left the runtime contradicting ecosystem.dev.config.js, which recovery required pm2 delete commander-dev && pm2 start ecosystem.dev.config.js to undo.

Where it is written. CMD-DEV-STD-001.md §5 (standing autonomous authority, with the --update-env prohibition and the recovery command); docs/RUNBOOK-PM2-OPERATIONS.md.

Leave ladder Promote to the operator's hand

Do this. Agents prepare a promotion — commit the slice, cut the release (version bump, CHANGELOG.md, tracking/releases/vX.Y.Z.md), make verify pass — and stop. The operator presses Promote. The ladder is the only path to prod: never sync a prod checkout by hand, and never restart prod or write to the prod database.

What breaks otherwise. Prod hosts live sessions, so a promotion is a user-visible event that needs a human choosing the moment. A manual prod checkout also blinds the Deploy badge, because the ladder's recorded state and the filesystem stop agreeing — the 2026-07-13 scar.

Where it is written. CMD-DEV-STD-001.md §5 (PENDING list) and §8 C2 (release cut is agent authority, release deployment is not).

Never code around a 428

Do this. A 428 Precondition Required from PATCH /api/settings means the write touches the login seam and needs proof of the current password in the x-current-password header of the same request (RFC 6585 signalling, NIST SP 800-63B §5 reauthentication). An agent holding a session token does not have that proof and should not try to obtain one. Report the refusal, name the keys, and leave the change to the operator. The same applies to the 422 that refuses an incoherent login write — for example auth_mode: oidc with no issuer configured, which would leave the instance with no working login path.

What breaks otherwise. On 2026-08-16 an agent session holding a valid Commander token rewrote login-critical settings through the generic settings PATCH and locked the operator out. The root cause was that possession of a session token proved nothing about knowing the credential it was minted from, yet sufficed to replace that credential. lib/auth-guard.js closed that. Any mitigation that weakens the guard — including "just set the key directly in the database" — re-opens it, and is by definition operator-hand.

Where it is written. lib/auth-guard.js header comment; CMD-DEV-STD-001.md §5 login-seam clause; recovery in docs/RUNBOOK-LOGIN-LOCKOUT.md.

Treat the login seam as read-only

Do this. The seam is the set of things that decide who can log in: the guarded settings keys app_password, auth_mode, every auth_oidc_* key and mcp_auth_base_url; the guard itself, lib/auth-guard.js; and the login-relevant behaviour of lib/auth.js and lib/auth-oidc.js. An agent reads these and proposes changes; an operator makes them. Note the guard's own shape: the floor list plus the auth_oidc_ prefix is enforced in code, and the AUTH_CRITICAL_KEYS_EXTRA environment variable may only add keys, never remove them — a guard list writable through the surface it guards would guard nothing.

What breaks otherwise. The prefix rule exists because the original enumerated floor omitted auth_oidc_allowed_subjects, so a token-bearer could have added their own IdP subject with no step-up. Enumerating a subset is how that class of hole appears; guarding a namespace is how it stays closed.

Where it is written. CMD-DEV-STD-001.md §5 (v1.1 login-seam freeze, added the same day as the incident); lib/auth-guard.js.

Pin every subagent's model, and never to Fable or Mythos

Do this. Orchestration — triage, adversarial vetting, hardening decisions, synthesis — stays in the main loop. Targeted subagents are spawned with model set explicitly: opus for analysis, code and subtree decisions; sonnet for mass or mechanical work; haiku for the cheapest fan-out. If the tier you want is unavailable, degrade down the permitted ladder or do the step in the main loop.

What breaks otherwise. On 2026-08-23 a fan-out research workflow let its subagents inherit Fable from the main loop, hit an Anthropic session rate limit mid-run, and lost two research rounds. Fable and Mythos are the orchestration tier; spending them on mass legwork risks a token outage that stalls the whole session. Inheritance is the mechanism, which is why "set it explicitly" is the rule rather than "prefer a cheaper model". A PreToolUse hook, .claude/hooks/block-fable-subagent.sh, blocks an explicitly requested Fable/Mythos subagent with exit 2 — but it states its own limit honestly: it cannot see a model inside a workflow's inline script, nor an inherited one. The policy is the control; the hook is a backstop.

Where it is written. CMD-DEV-STD-001.md §5A.

Change dev schema only through node migrate.js

Do this. Every schema change is a numbered file in migrations/, applied with node migrate.js and nothing else — on dev as strictly as on prod. DATABASE_URL is required and has no default, so the runner cannot guess a database. Additive-first, so a stale server still reads the old shape.

What breaks otherwise. Applying SQL by hand leaves commander.schema_migrations disagreeing with the directory. The ledger records each file with a SHA-256 checksum (FIPS 180-4) inside the same transaction that applied it, so drift is detected — and the migrate rung of the next promotion halts on it. Reconciling a drifted ledger is manual work with no undo. The single sanctioned in-code DDL is the ledger table's own bootstrap in migrate.js; anything else is an out-of-band schema change and a red gate cell.

Where it is written. CLAUDE.md non-negotiable 2; CMD-DEV-STD-001.md §3.3.

Keep secrets in secrets.env, outside every worktree, at mode 0600

Do this. Encryption keys, database URLs and provider tokens live in one secrets.env file outside all worktrees, mode 0600, loaded by the pm2 process config and by scripts/vault.js. Never in a tracked file, never echoed back once stored. Rotate in the order: rotate the value, update secrets.env, restart the service.

What breaks otherwise. A worktree is copied, promoted and sometimes committed; a secret inside one leaks through any of those paths. seed-projects.js is the local proof — it once shipped two live keys, is now environment-sourced, and those keys are on the rotation list. PTY_ENV_BLOCKLIST keeps the same values out of the environment that agent terminals inherit, so a secret that never enters the worktree also never reaches a session.

Where it is written. docs/SECURITY-P0-ROTATION.md; CLAUDE.md known standing violations.

Never touch the protected infrastructure

Do this. myorg.ai, portal.myorg.ai and library.myorg.ai are separate projects with their own databases and processes. An agent working on Commander does not read their databases, restart their processes, or change their DNS or hosting. The same applies to backup-script changes owned by the platform-management repository.

What breaks otherwise. These services have their own release cycles and their own operators. A change made from inside Commander's session is a change nobody who owns that service has reviewed, on a host where a mistake is shared.

Where it is written. .claude/QUICKSTART.md, "Protected Infrastructure"; CMD-DEV-STD-001.md §5 (infrastructure outside this project is operator-hand).

Vet at least ten angles, adversarial included, before any change

Do this. Any option, change or feature is vetted across at least ten angles and the vetting is presented with the proposal, not after it. The baseline list: adversarial abuse and injection; security posture and least privilege; backward compatibility and migration path including stale-client skew; configuration purity; failure modes and degradation; observability and debuggability; multi-tenancy and marketplace portability; standards compliance with the document cited; maintenance burden and sprawl measured in net lines; testability and a verification plan; vendor lock-in and exit cost; performance on the hot path. Extend the list to fit the task. After a set of related tasks, re-vet the actual diff without waiting to be asked, and record it at work/vet/<SET-ID>.md; per-item evidence goes to work/impact/<ITEM-ID>.json.

What breaks otherwise. Every rule on this page is a vetting angle someone skipped once. A finding that is enumerated and dispositioned — fixed, scheduled with an owner, or recorded — is cheap; a finding that is silently dropped comes back as an incident. Red-cell semantics fail closed: a missing record blocks the next item rather than letting it proceed quietly.

Where it is written. CLAUDE.md, "Mandatory vetting"; CMD-DEV-STD-001.md §2 (R1/R3) and §3.5 (a rotten or false-positive gate is surfaced, never worked around).

Keep configuration pure

Do this. Every tunable routes environment variable → commander.settings/api/client-config. A literal is legal only as the declared, named default at the top of that chain — never inline at the point of use. Values the operator should be able to change at runtime go in the lib/tunables.js registry and are read at use time. The client renders from served config and hardcodes nothing; the module-level defaults in the SPA are stale-server fallbacks only.

What breaks otherwise. A value settable in three places with no defined order is three bugs waiting, and one of them will be found on a host you cannot log into. Config purity is also what lets one build serve a personal host, a team instance and a multi-tenant container — the Snowflake Marketplace direction depends on it. Invalid configuration warns once and falls through to the next rung of the chain: never fail silent, and never honour a value you could not parse.

Where it is written. CLAUDE.md non-negotiable 1; CMD-DEV-STD-001.md §3.2; the mechanism in developer.

Reference

Rule table

RuleScopeWho may do itWhere recorded
Generated credentials go into the Password Vault, same stepany password, token or key an agent creates or resetsagent or operator (required of both)CLAUDE.md; commander.vault_entries
No commander-dev restart while in use; never --update-envdev instance lifecycleagent, in an agreed windowCMD-DEV-STD-001.md §5; docs/RUNBOOK-PM2-OPERATIONS.md
Ladder Promote to prod; prod restarts; prod DB writesproductionoperator onlyCMD-DEV-STD-001.md §5, §8 C2
Never code around a 428 or 422 from the settings guardlogin-seam writesoperator onlylib/auth-guard.js; CMD-DEV-STD-001.md §5
Login seam read-only: app_password, auth_mode, auth_oidc_*, mcp_auth_base_url, lib/auth-guard.js, lib/auth*.jsauthenticationoperator onlyCMD-DEV-STD-001.md §5 (v1.1)
Subagent model set explicitly; Opus / Sonnet / Haiku, never Fable or Mythosagent orchestrationagent (hook-backstopped)CMD-DEV-STD-001.md §5A; .claude/hooks/block-fable-subagent.sh
Schema changes only as a numbered migration via node migrate.jsdev and prod databasesagent on dev; operator for destructive migrationsCLAUDE.md non-negotiable 2; CMD-DEV-STD-001.md §3.3
Secrets in secrets.env, 0600, outside every worktreeall credential materialagent or operator; rotation of live credentials is operator-handdocs/SECURITY-P0-ROTATION.md
Protected infrastructure untouched: myorg.ai, portal.myorg.ai, library.myorg.aiother services on the hostoperator only.claude/QUICKSTART.md; CMD-DEV-STD-001.md §5
≥10-angle vetting, adversarial included, presented with the proposalevery changeagent and operatorCLAUDE.md; CMD-DEV-STD-001.md §2
Config purity: env → settings → /api/client-config, literal only as named defaultevery tunableagent and operatorCLAUDE.md non-negotiable 1; CMD-DEV-STD-001.md §3.2
Never test against prodall workagent and operatorCLAUDE.md context

What each artefact is for

ArtefactPathWhen
Impact Recordwork/impact/<ITEM-ID>.jsonevery work item, docs-only included
Vet recordwork/vet/<SET-ID>.mdafter each set of related tasks
Feature spectracking/features/FEAT-NNN.mdbefore the feature is built
Shortcut / debt entrytracking/, with a remediation planbefore the shortcut lands
Release notestracking/releases/vX.Y.Z.md + CHANGELOG.mdat the release cut (SemVer 2.0)
Seam changea row in docs/federation/INTEGRATION-REGISTRY.mdin the same commit as the change
Pointer of recorddocs/sprints/next-task.txtread at session start; superseded blocks retained

Explanation

Why the rules name incidents. Each rule above carries a date and a failure. That is deliberate. A rule stated as a principle invites an agent or a new contributor to reason about whether this case is an exception; a rule stated as "this happened, here is what it cost" does not. It also makes the rules auditable: when the incident no longer applies, the rule can be retired on the evidence instead of lingering as folklore.

Why an agent is never its own approver. The pair here is one operator and one agent, so several controls that assume a second reviewer have no second reviewer available. The honest response is not to invent one. Where a control expects review that does not exist, the act is either left pending for the operator or made reversible, and the Impact Record attests to which — never a fabricated second identity, never self-approval. That is why the operator-hand list is longer than the fleet baseline while the autonomy list is narrower: authority was earned rung by rung through repeatable, operator-validated cycles, and prod was never one of the rungs.

Why reversibility is the dividing line. An agent proceeds without an operator turn when the work is git-revertable, inside its scoped item, with all machine gates honestly green, and with no open disagreement between the pointer, the code, the database and the pm2 runtime. Everything on the far side of that line — a live credential, a destructive migration, an external publication, a prod mutation — cannot be undone by git revert, which is exactly why it waits for a human.

Why "never fail silent" is a practice and not just a coding style. A silent fallback is indistinguishable from correct behaviour until something depends on it. So an unparseable configuration value warns once and falls through with its reason logged; a refused write names the key; a halted promotion shows the queued commits; a licence-plane error is a structured problem document. The corollary is the one about gates: a test layer that skipped, a check that passed for the wrong reason, a green that came from an empty run — surface it. A rotten gate is worse than a red one, because a red gate stops you.

You've seen the proof

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

Get your quote →