> For AI agents: the complete documentation index is available at https://docs.phax.run/llms.txt, the full documentation bundle is available at https://docs.phax.run/llms-full.txt.

# phax 0.34.0 \{#phax-0340}

Changes since 0.33.0: [v0.33.0...v0.34.0](https://github.com/lbdremy/phax/compare/v0.33.0...v0.34.0).

## Highlights \{#highlights}

- **Command contracts** — An agent can rely on any phax command's answer without reading its source, and a person notices nothing but a new flag.
- **Project-only Claude sessions** — A headless session sees what the repository committed, and only that; a session a person drives is theirs.

## Upgrading \{#upgrading}

- **Gate steps accept `partial`.** An optional `partial` object with a `files` and/or a `diff` command, joined by pipes only, is shown in the phase prompt and allowed in secure mode; a malformed one is refused when the config loads, as is a quote, a backslash or a `*` in it. `phax validate` and `phax run --dry-run` list the prefixes each profile's partial commands allow (the `--json` answers gain `partialPrefixes`). See [Partial commands](https://github.com/lbdremy/phax/blob/v0.34.0/README.md#partial-commands). Records readers: `security.json` `agentCommands[].source` gains the value `"partial"`; the file stays at version 3. No migration is needed.
- **Command line: a refused call exits 64.** An unknown command, an unknown option or a bad argument (a missing argument or option value) now exits 64 instead of 1, with the same text. A group called alone, such as `phax artifact`, writes its help on stderr and exits 64. A command answering through the runner refuses an operand it does not declare, which it ignored before: `phax validate extra` and `phax ls extra` exit 64. A script that checks for exit 1 on a mistyped call checks for 64. The page [Machine answers](https://github.com/lbdremy/phax/blob/v0.34.0/docs/json-output.md) describes `--json`, the message lines and `--json-schema`.
- **Command line: an unexpected crash exits 70.** It writes `defect: …` on stderr, where it wrote `Unexpected error: …` and exited 1. A command's own refusals keep their codes, and the phase guard still refuses with exit 1.
- **Command line: `phax --json-schema` prints the contracts of the commands answering through the runner**, with the program's exit codes and the shapes of its message lines. `--json-schema` after a command that does not answer through the runner is refused with exit 64.
- **Run list: `phax ls --json` rows gain the required `pullRequest`.** It is the URL of the pull request phax opened for the run, or `null` when it opened none or the run's publication record cannot be read. `run-list` is stamped 0.34.0; a list stamped 0.26.0 or 0.30.0 is still parsed by `@lbdremy/phax-schemas` (`parseRunList`), and `toLatestRunList` gives each of its rows `pullRequest: null`. A script that matches the row's keys exactly adds the new one.
- **Schemas package: `artifact-status`, `plan-overview` and `plan-lint` are new formats at 0.34.0.** `@lbdremy/phax-schemas` exports `parseArtifactStatus`, `parsePlanOverview` and `parsePlanLint`, with their schemas, `toLatest` functions and JSON Schemas, and `parseDocument` identifies each. They describe the answers of `phax artifact status --json`, `phax plans overview --json` and `phax plans lint --json`, which phax never saves.
- **Plans: `phax plans overview --json` and `phax plans lint --json` answer versioned documents.** Each prints a `plan-overview` or `plan-lint` document whose first key is `$schema`, followed by the keys the unversioned object had, in the same order. A script that reads the object as it was ignores `$schema`, or reads it with `parsePlanOverview` and `parsePlanLint`. `plans lint` still exits 1 when a finding is an error, with or without `--json`.
- **Artifacts: `phax artifact status --json` is new.** It prints an `artifact-status` document: the path, kind, status, legal transitions, approval, authoring and questions the text reports. Without `--json` the text is unchanged.
- **Command line: `artifact status`, `plans overview`, `plans lint` and `ls` answer through the runner.** Under `--json`, stderr carries only message lines, one JSON object each: a warning (an orphan approval record, a failed architecture provider) is a `note` line, a failure an `error` line, a refused call a `refused` line. `--json-schema` after each prints its contracts. Without `--json`, their text, warnings, error sentences and exit codes are unchanged.
- **Command line: the help of a command answering through the runner** prints its long help before its options, wrapped at 80 columns; its usage line, options, help texts and examples are the same. `phax help <command>` and `phax <group> help <command>` print it too.
- **Plans: `plans status`, `plans overlap` and `plans estimate` answer through the runner.** Their `--json` objects are unchanged except `plans status --apply`, which gains `written` after `applied`, the flipped plans' records. Under `--json` their warnings are note lines. `phax plans` alone and `--json-schema` follow the runner.
- **Command line: `validate`, `session-info`, `path`, `brief`, `architecture`, `completions` and `report` answer through the runner.** Under `--json` each answers one JSON object and message lines on stderr, and `--json-schema` after each prints its contracts. Their text and codes are unchanged; `phax path` prints the same bytes. `phax completions` prints a script that embeds the regenerated usage spec (the exit-code block after the root help, `--json-schema`, each command's `--json`), so its bytes change while completion works as before.
- **Command line: `cost backfill`, `schema upgrade`, `security status` and `skills install` answer `--json`.** The writing ones list the files they wrote as written-file records (`cost backfill` the cost-ledger.json files, by absolute path; `schema upgrade` the two schema files; `skills install` each installed file), and `skills install` names the files already installed when it fails part way. Without `--json` each prints the same text with the same codes. The `cost`, `schema`, `security` and `skills` groups called alone are refused with exit 64 and their help; bare `phax security`, which ran the status report and exited 0, now does the same, so call `phax security status`. `--verbose` and `--trace` on `security` and `security status` are the program's options, accepted anywhere in the call as before.
- **Command line: the `records` commands answer `--json`.** `records init` prompts, so `records init --json` is refused with exit 64 before anything is asked, although its help lists `--json` as every command's does. `records explain --json` carries the text of each artifact its flags ask for (`--prompt`, `--diff`, `--transcript`, `--gates`, `--briefs`), `null` when the record does not carry it. Without `--json` the text, error sentences and exit codes are unchanged, and `phax records` alone is refused with exit 64 and its help.
- **Artifacts: the transitions, `artifact schema` and the migrations answer through the runner.** Under `--json`, `artifact approve`, `reopen`, `stale`, `abandon` and `complete` answer an object (the status reached, the previous status, the path, the baseline, who approved or reopened, the approval record a reopen, complete or abandon deleted, the bound plans, the commit) and list the files they wrote as written-file records (a headless artifact's sidecar only when complete or abandon moves it); `artifact migrate-approvals` and `artifact migrate-documents` answer their report with the record files, or the sidecars and bodies, they wrote. `approve --json` and `reopen --json` without `--by` are refused with exit 64 before anything is written, because a terminal standing for the operator is a person's gesture; the refusal reads `error: --json: artifact approve prompts; pass --by`. `artifact schema` prints the same bytes in both modes. Under `--json`, `migrate-approvals`' orphan-record warnings are note lines on stderr. An unknown `artifact schema` kind now exits 64, with the runner's text.
- **Artifacts: `artifact decide` and `artifact new spec|plan` answer through the runner.** `--verdicts <file|->` and `--brief <file|->` are read before anything runs: a terminal on stdin, a file that cannot be read, and a verdicts file that does not decode or does not fit the artifact (another artifact's path, an unknown question or option, a question named twice, a missing note) are refused with exit 64 and a refused line naming the file or stdin and the field, such as `error: verdicts.json: verdicts[0].chosen is not an option of q-cadence`; a file stamped with an older decide-verdicts release is refused the same way, at `$schema`. These sentences no longer list the question's options or the artifact's question ids, and a field the file's schema refuses is written `verdicts.0.note`. Before, `decide` answered these with exit 12 and `artifact new` waited on a terminal stdin. `--brief` given without `--headless` is now read, and refused when unreadable. `decide --json` without `--verdicts`, or without `--by`, is refused with exit 64 before anything starts, where it exited 12 with `--json needs --verdicts`; the refusal reads `error: --json: artifact decide prompts; pass --verdicts and --by`. `decide --verdicts --json` prints its object two-space indented, not on one line, with `written` (the files its commit holds) and `record` (the decision record's outcome) after its twelve keys, and its `record` line no longer reaches stderr. `artifact new --json` answers the path, kind and lineage of a blank skeleton, or with `--headless` the path, sidecar, commit and authoring record. Under `--json` the sessions' progress lines (`authoring …`, the decide session's lines) are note lines on stderr, and a failed commit's error line lists the files it left written. `phax artifact` and `phax artifact new` alone are refused with exit 64 and their help on stderr, as `phax agent` and `phax agent setup` are: the help puts a group's long help before its commands, lists `help [command]` last, and ends with ``Run `phax --help` for usage.``. `phax artifact archive` keeps its sentence but exits 64, not 1, follows it with ``Run `phax --help` for usage.``, and answers `--help` with that refusal, not with help. The help of `decide` and `artifact new spec|plan` lists `--verdicts` and `--brief` after the other options, beside a `stdin: JSON` or `stdin: text` line. Without `--json` both commands print the same text with the same codes otherwise.
- **Agent: `agent models`, `resolve`, `probe`, `setup providers` and `setup mistral-vibe` answer through the runner.** Each answers `--json` with one indented object (`models` the routing table and priority, `probe` each provider's availability and whether config enables it, the setup commands what they did). `agent resolve --json` is unchanged in its bytes. The setup commands list the files they wrote (providers.json, \~/.vibe/config.toml, their backups, a scaffolded model-routing.json) as written-file records with absolute paths. Under `--json`, an invalid `--effort` or a failed setup is one `error` line on stderr. Without `--json` the text, error sentences and exit codes are unchanged. `phax agent` and `phax agent setup` alone are refused with exit 64 and their help.
- **Root commands: `init`, `unlock`, `archive` and `prune` answer `--json`.** `init --yes --json` answers the config and schema paths, whether the wizard ran interactively, and phax.json, phax.schema.json and phax.user.schema.json as written-file records; `init --json` without `--yes` is refused with exit 64 before anything is asked or written, even off a terminal, naming `pass --yes`. `unlock --json` answers `{ shortName, removed, qualifiedName }`, `removed` one of `stale` (the holder's process is gone), `expired` (the lock outlived its lifetime), `forced` or `none`. `archive --json` answers `{ qualifiedName, crossProject }`. `prune --json` keeps its object byte for byte, but its preview lines (`Prune from namespace …`) are now note lines on stderr, and `prune --json` without `--yes` or `--dry-run` is refused with exit 64 and one refused line, `error: --json: prune prompts; pass --yes to proceed or --dry-run to preview only`, where it printed the preview document and exited 1. A held lock, a refused archive and a failed prune are one `error` line under `--json`. Without `--json` the text, error sentences and exit codes are unchanged, and `prune` keeps `-y` beside `--yes`.
- **Review: `review-handoff`, `publish-pr`, `review-compliance` and `review-code` answer through the runner.** Under `--json` each answers one object: the run's qualified name and whether it is another project's, then `reviewHandoff` and `written` for `review-handoff`, `pullRequest` (the URL, or `null` when the branch was pushed without opening one) for `publish-pr`, `verdict`, `review`, `notice`, `warnings` and `written` for `review-compliance`, and for `review-code --headless` `findings` (a count and its summary), `findingsPath`, `fixHint`, `notice`, `warnings` and `written`. `review-handoff` lists the reconciliation, `review-handoff.md` and `final-report.md` it regenerated, `review-compliance` its Markdown and JSON, and `review-code --headless` `code-review.json` and `code-review-session.json`, each as a written-file record. The records and push warnings are note lines on stderr under `--json`. `review-code --json` without `--headless` is refused with exit 64 before any session starts, since it hands the terminal to a session; without `--json`, its exit is still the session's. Without `--json` the text, streams and codes are the same. In `phax --help`, `publish-pr` and `review-compliance` show `[options]` in their usage, since they now take `--json`.
- **Sessions: `enter`, `enter-phase`, `shell`, `open` and `adjust-plan` answer through the runner.** Each hands the terminal to a session, a shell or an editor, so under `--json` each is refused with exit 64 and one `refused` line before anything is read or started, naming the command that answers instead: `phax session-info <run> --json` for `enter` and `enter-phase`, `phax path <run> --json` for `shell` and `open`, and for `adjust-plan` that it runs from a terminal without `--json`. Before, `--json` was an unknown option. Without `--json` each prints the same lines and exits with the code of the process it handed the terminal to, as before; their error sentences and codes are unchanged. `shell` starts `$SHELL` (or `bash`) through the session port, and `adjust-plan` reads its plan through the file system port. In `phax --help`, `enter`, `shell` and `open` show `[options]` beside their name.
- **Runs: `run`, `resume` and `reset-phase` answer through the runner.** Under `--json` each answers one object naming its outcome: `run` and `resume` answer `{ qualifiedName, crossProject, outcome }`, the outcome a `dry-run` (the report `--dry-run` prints), a `preview` (resume without `--yes`: the state, the next phase, the skipped phases, the owed step), `already-in-review`, `review_open` (the phase count, the pull request, the completed artifacts and the final report's path), `stopped` (exit 13, with the stop report) or `paused`; `reset-phase` answers its `preview` or its `reset`. Every progress line (`Run: <run>`, `using cached extraction`, `would resume …`, the extract warnings, the `--verbose` events) is a note line on stderr under `--json`, written as it happens, so the order of stdout against stderr is no longer a contract there. A paused run (a rate or usage limit, a lost network, a handoff turn, gates exhausted or a refused gate step, a phase with no changes, a refused close) is an answer, not an error: the outcome `paused` names its reason, phase, resume command, resume-instructions path, `resetAt` when known and its exit, and the call exits with that code, as before. A refusal or failure before the run starts or outside a pause is one `error` line with today's code; a held run lock is `LockConflictError` with exit 7 and fields `run` (the short name) and `pid`. `resume` and `reset-phase` without `--yes` answer their preview under `--json` and take no lock. Every exit code stays. `resume --skip-oracle` keeps its sentence but exits 64, not 1, and follows it with ``Run `phax --help` for usage.``. `resume` and `reset-phase` no longer list their own `--verbose` and `--trace`, in their `--help` or in the usage spec: they are the program's options, accepted anywhere in the call as before. Under `--json`, auto-publish's `--verbose` lines (`Publishing branch to …`, `Pull request created: …`) and the run-end reviews' `[phax] …` lines are note lines too, as are `publish-pr --verbose`'s. An interrupted `run` or `resume` (SIGINT or SIGTERM) still saves the run and exits 130; under `--json` its sentence is note lines. A run of short flags is read as one word: `resume x -yv` is refused naming `-yv`, where it named `-v`. The help of `resume` no longer says it prompts: without `--yes` it prints where it would resume and exits 0. Without `--json` the text, streams and codes are unchanged; `-y` stays beside `--yes`, and `run --hold` keeps its optional value.
- **Command line: every command answers through the runner.** `phax --json-schema` lists every command, and `--json-schema` after any command prints its contracts; it is no longer refused after a command. `phax help <command>` answers that command's help, and `phax help <group>` its group's. phax.usage.kdl is written in one style, from the command definitions, under a two-line header: its groups no longer declare `subcommand_required` (a group called alone is still refused with exit 64 and its help), its program name is a bare word (`name phax`), and its global flags list `--usage` and `--usage-format` before `--verbose` and `--trace`. A script that reads the file with the `usage` CLI sees the same commands, arguments and flags. `--version`, `-V` and `--usage` print as before when called alone. Root help gains a `--json-schema` line under Options; `path`, `brief`, `architecture` and `completions` show `[options]` beside `enter`, `shell`, `open`, `publish-pr` and `review-compliance`, and `security` no longer does. `--help` and `--version` are answered alone: with any other word (`phax --help run`, `phax run --version`, `phax -V run`) the call is refused with exit 64, where 0.33.0 printed the help or the version. Bare `phax`, or `phax` with only program options (`phax --verbose`), writes the root help on stderr and exits 64, not 1, ending with ``Run `phax --help` for usage.``. `phax <group> help [command]` answers like `phax <group> [command] --help`. The phase guard refuses inside a phase worktree with today's sentence and exit 1, or one `refused` line under `--json`, while `--help`, `-h` and `--json-schema` still answer; it now runs before the call is read, so a malformed call there (`phax run` without `--plan`) gets the guard's sentence rather than the missing option's, and a group alone gets its help with exit 64.
- **Claude Code: headless sessions load only the repository's harness.** Phase sessions, handoff, authoring, decide's handback, review and structured completions no longer load your Claude Code user settings, skills, plugins, hooks, instructions or auto-memory; interactive sessions and the usage probe are unchanged. The repository's `CLAUDE.local.md` and `.claude/settings.local.json` are left out too. Your own `permissions.deny` rules and hooks no longer guard headless sessions: in unsafe mode nothing else blocks what they blocked, so move a guardrail every session must keep into the committed `.claude/settings.json` or managed settings. If you authenticate through user settings (an `apiKeyHelper`, an `env` block, Bedrock or Vertex set there), run `claude /login` or set `ANTHROPIC_API_KEY` in the environment phax runs in, or, for Bedrock, Vertex or a gateway, export `CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX` or `ANTHROPIC_BASE_URL` and its credentials there; otherwise the phase fails with `not-authenticated` and names the remedies. If another credential is available (a `claude /login` session, `ANTHROPIC_API_KEY`), a headless session uses it silently instead of your settings' backend. A setting a headless session needs goes in the repository's committed `.claude/settings.json`. Phase records carry the required `userConfig` (`excluded` for claude-code, `loaded` for codex-cli and mistral-vibe), and `phase-record-manifest` and `decision-record-manifest` are stamped 0.34.0; earlier records read as `loaded`, and `excluded` covers a phase's headless sessions only. See [What a Claude Code session loads](https://github.com/lbdremy/phax/blob/v0.34.0/docs/security.md#what-a-claude-code-session-loads).

Later entries of this release follow.

## What's new \{#whats-new}

### Command contracts \{#command-contracts}

> An agent can rely on any phax command's answer without reading its source, and a person notices nothing but a new flag.

- Every phax command answers with JSON on stdout when given `--json`, so an agent or script can read any command the same way. `--json on every command`

  Now:

  ```text
  $ phax unlock garden-sync --json
  {
    "shortName": "garden-sync",
    "removed": "stale"
  }
  $? = 0
  ```

  :::details Before

  ```text
  $ phax unlock garden-sync --json
  error: unknown option '--json'
  Run `phax --help` for usage.
  $? = 1
  ```

  :::

- With `--json`, stderr carries one JSON object per line — a note, a written file, an error, a refusal or a defect — so an agent can act on a failure without parsing a sentence. `message lines on stderr under --json`

  Now:

  ```text
  $ phax resume garden-sync --json
  {"kind":"note","text":"Resuming garden-sync at phase-03"}
  {"kind":"error","error":"LockConflictError","exit":7,"fields":{"run":"garden-sync","pid":41207},"written":[]}
  $? = 7

  $ phax plans lint --colour docs/plans/2611011200-garden-sync-plan.md --json
  {"kind":"refused","source":"flags","path":["colour"],"expected":"no such option","text":"error: unknown option '--colour'","exit":64}
  ```

  :::details Before

  ```text
  $ phax resume garden-sync
  Run garden-sync is locked by pid 41207 (started 2026-11-03 09:12).
  Wait for it to finish, or remove a stale lock with: phax unlock garden-sync
  $? = 7
  ```

  :::

- `phax --json-schema` prints every command's input, output and error contracts, and `phax <command> --json-schema` prints one command's, without running anything. `--json-schema`

  Now:

  ```text
  $ phax plans lint --json-schema
  {
    "name": ["plans", "lint"],
    "input": { "type": "object", "properties": { "plan": { "type": "string" } } },
    "stdin": null,
    "output": { "$ref": "https://docs.phax.run/schemas/plan-lint/0.34.0.json" },
    "errors": [ { "tag": "ConfigValidationError", "exit": 2, "fields": { "type": "object" } } ]
  }

  $ phax --json-schema
  {
    "program": "phax",
    "version": "0.34.0",
    "exits": { "refused": 64, "defect": 70 },
    "commands": [ … ],
    "messages": { … }
  }
  ```

- `phax artifact status <path> --json` answers with a versioned document: the artifact's status, legal transitions, approval, authoring and open questions. `phax artifact status --json`

  Now:

  ```text
  $ phax artifact status docs/specs/2611011200-garden-sync.md --json
  {
    "$schema": "https://docs.phax.run/schemas/artifact-status/0.34.0.json",
    "path": "docs/specs/2611011200-garden-sync.md",
    "kind": "spec",
    "status": "Draft",
    "legalTransitions": ["Approved", "Abandoned"],
    "approval": { "kind": "none" },
    "authoring": { "kind": "headless", "sidecar": "docs/specs/2611011200-garden-sync.json", "agreement": "in-sync" },
    "questions": { "kind": "parsed", "open": ["q-cadence"], "arbitrated": ["q-store"], "lifted": [] }
  }
  ```

  :::details Before

  ```text
  $ phax artifact status docs/specs/2611011200-garden-sync.md
  Path:              docs/specs/2611011200-garden-sync.md
  Kind:              spec
  Status:            Draft
  Authored:          headless — sidecar docs/specs/2611011200-garden-sync.json (in sync)
  Questions:         2 — open 1 (q-cadence) · arbitrated 1 (0 lifted)
    decide with `phax artifact decide docs/specs/2611011200-garden-sync.md`
  Legal transitions: Approved (once no question is open), Abandoned
  ```

  :::

- `phax plans overview <plan> --json` now answers with a versioned `plan-overview` document a reader can validate. `phax plans overview --json`

  Now:

  ```text
  $ phax plans overview docs/plans/2611011300-garden-sync-plan.md --json
  {
    "$schema": "https://docs.phax.run/schemas/plan-overview/0.34.0.json",
    "plan": "docs/plans/2611011300-garden-sync-plan.md",
    "name": "garden-sync",
    "title": "Garden sync",
    …
  }
  ```

  :::details Before

  ```text
  $ phax plans overview docs/plans/2611011300-garden-sync-plan.md --json
  {
    "plan": "docs/plans/2611011300-garden-sync-plan.md",
    "name": "garden-sync",
    "title": "Garden sync",
    …
  }
  ```

  :::

- `phax plans lint <plan> --json` answers with a versioned `plan-lint` document, and still exits 1 when a finding is an error. `phax plans lint --json`

  Now:

  ```text
  $ phax plans lint docs/plans/2611011300-garden-sync-plan.md --json
  {
    "$schema": "https://docs.phax.run/schemas/plan-lint/0.34.0.json",
    "plan": "docs/plans/2611011300-garden-sync-plan.md",
    "findings": [
      { "severity": "error", "check": "structure", "phase": "phase-02", "message": "phase-02 has no gate" }
    ]
  }
  $? = 1
  ```

  :::details Before

  ```text
  $ phax plans lint docs/plans/2611011300-garden-sync-plan.md --json
  {
    "plan": "docs/plans/2611011300-garden-sync-plan.md",
    "findings": [
      { "severity": "error", "check": "structure", "phase": "phase-02", "message": "phase-02 has no gate" }
    ]
  }
  $? = 1
  ```

  :::

- Each run in `phax ls --json` now carries the URL of the pull request phax opened for it. `phax ls --json`

  Now:

  ```text
  {
    "$schema": "https://docs.phax.run/schemas/run-list/0.34.0.json",
    "runs": [
      { "qualifiedName": "garden.garden-sync", "state": "review_open", "branch": "phax/garden-sync", …, "stop": null,
        "pullRequest": "https://github.com/acme/garden/pull/42" }
    ]
  }
  ```

  :::details Before

  ```text
  {
    "$schema": "https://docs.phax.run/schemas/run-list/0.30.0.json",
    "runs": [
      { "qualifiedName": "garden.garden-sync", "state": "review_open", "branch": "phax/garden-sync", …, "stop": null }
    ]
  }
  ```

  :::

- A mistyped command or option, and an unexpected crash, now exit with their own documented codes instead of the generic 1. `refused and defect exit codes`

  Now:

  ```text
  $ phax plans lnit docs/plans/2611011300-garden-sync-plan.md
  error: unknown command 'lnit'
  (Did you mean lint?)
  $? = 64
  ```

  :::details Before

  ```text
  $ phax plans lnit docs/plans/2611011300-garden-sync-plan.md
  error: unknown command 'lnit'
  (Did you mean lint?)
  $? = 1
  ```

  :::

- A command reading a document from stdin is refused at once when stdin is a terminal, and a document that does not decode is refused before anything runs, naming the field. `documents read from stdin`

  Now:

  ```text
  $ phax artifact decide docs/specs/2611011200-garden-sync.md --verdicts - --by operator
  error: --verdicts - reads stdin, which is a terminal: pipe the verdicts in or name a file
  $? = 64

  $ phax artifact decide docs/specs/2611011200-garden-sync.md --verdicts verdicts.json --by operator --json
  {"kind":"refused","source":"stdin","path":["verdicts",0,"chosen"],"expected":"an option id of q-cadence","text":"error: verdicts.json: verdicts[0].chosen is not an option of q-cadence","exit":64}
  ```

  :::details Before

  ```text
  $ phax artifact decide docs/specs/2611011200-garden-sync.md --verdicts - --by operator
  (waits for input on the terminal)
  ```

  :::

- The usage spec lists `--json` on every command and is otherwise unchanged: same commands, arguments, options, help and completions. `phax.usage.kdl`

  Now:

  ```text
  cmd "lint" {
      help "Report a plan's mechanical defects without running it"
      arg "<plan>" {
          help "Path to the plan.md"
      }
      flag "--json" {
          help "Write the answer as JSON on stdout, and messages as JSON lines on stderr"
      }
  }
  ```

  :::details Before

  ```text
  cmd "lint" {
      help "Report a plan's mechanical defects without running it"
      arg "<plan>" {
          help "Path to the plan.md"
      }
      flag "--json" {
          help "Emit the findings as JSON"
      }
  }
  ```

  :::

### Project-only Claude sessions \{#project-only-claude-sessions}

> A headless session sees what the repository committed, and only that; a session a person drives is theirs.

- A phase record now says whether the operator's own provider configuration was in play for the phase's headless sessions. `<run>/<phase>/record.json`

  Now:

  ```text
  {
    "$schema": "https://docs.phax.run/schemas/phase-record-manifest/0.33.0.json",
    "runId": "…",
    "phaseId": "02-reader",
    …
    "model": "claude-sonnet-5-5",
    "effort": "medium",
    "provider": "claude-code",
    "userConfig": "excluded",
    "outcome": …
  }

  # a codex-cli or mistral-vibe phase: "userConfig": "loaded"
  ```

  :::details Before

  ```text
  {
    "$schema": "https://docs.phax.run/schemas/phase-record-manifest/0.30.0.json",
    "runId": "…",
    "phaseId": "02-reader",
    …
    "model": "claude-sonnet-5-5",
    "effort": "medium",
    "provider": "claude-code",
    "outcome": …
  }
  ```

  :::

- An operator whose Claude Code login lived in their user settings is told that phax's headless sessions skip those settings and how to authenticate instead. `authentication failure of a headless session`

  Now:

  ```text
  $ phax run docs/plans/2611050900-reader-plan.md
  ✗ phase 01-schema: Claude Code is not authenticated
    phax's headless sessions do not load your Claude Code user settings
    (~/.claude/settings.json), so an apiKeyHelper or env set there is not used.
    Log in with `claude /login`, or set ANTHROPIC_API_KEY where you run phax.
  $? = 1
  ```

  :::details Before

  ```text
  $ phax run docs/plans/2611050900-reader-plan.md
  ✗ phase 01-schema: claude session failed (exit 1)
    Invalid API key · Please run /login
  $? = 1
  ```

  :::

## Fixes \{#fixes}

- fix(cli): review of command-contracts plan 2 ([095c1af](https://github.com/lbdremy/phax/commit/095c1afa16d571f689cf2091e4a017a004dd7a6e))
- fix(cli): keep --json clean on run, resume and publish-pr; restore group help ([2324e93](https://github.com/lbdremy/phax/commit/2324e932052823bf48d5d5565ae7f2ca85817647))
- fix(prompt): test the gate section per provider; say so when no step runs ([69a2550](https://github.com/lbdremy/phax/commit/69a25501610a9276890d5005878a5f08d226d4ea))
- fix(session): name the cloud remedies; keep a hook's "Not logged in" off Claude's login ([8fc1cd5](https://github.com/lbdremy/phax/commit/8fc1cd5d65923d9b0f0510e821c565d289b55a55))
- fix(gate): refuse quotes, escapes and globs in partial commands; list what they allow ([c9d51dd](https://github.com/lbdremy/phax/commit/c9d51dd2b0bef0c028b93a89af28de8b9cf8f5be))

## Other \{#other}

- feat(prompt): name the phase gate list and its contract in the phase prompt ([8f310ec](https://github.com/lbdremy/phax/commit/8f310ec7535cefb0d9a0f595273aa0e246fdcdda))
- feat(config): accept partial commands on gate steps ([d0550e5](https://github.com/lbdremy/phax/commit/d0550e54d567f3e438023f6a18d0bab77ef0a434))
- feat(gate): allow and show partial commands of gate steps ([ff05473](https://github.com/lbdremy/phax/commit/ff05473e36324e01c50d2ba2776d59ffbdcdc837))
