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, and this page is available as Markdown at https://docs.phax.run/guide/upgrading.md.

Upgrading phax

Upgrade the way you installed phax:

npm install -g @lbdremy/phax@latest

With a downloaded binary, download the new release from GitHub Releases and replace the old one.

After every upgrade

Two commands keep what phax copied into your repository in step with the new binary. Run both in each repository that uses phax, and commit what they change:

phax skills install --target claude   # the bundled skills (phax-planning, phax-spec, …); or codex, or agent
phax schema upgrade                   # phax.schema.json, the editor schema of phax.json

Release by release

Only the releases that ask for something are listed. phax never asks for a step silently: a command that needs one refuses with exit 12 and names it.

0.35.0

  • Commit trailers name no path of the machine. Phase, interactive and held review commits no longer carry Worktree or Session-Id, and Gate-Log is relative to the run's folder (phase-02/checks-attempt-01.log), or none. A stop whose commit was written by an older version still closes, and no record or run file changes. A script that read those trailers reads the phase folder instead: <run>/<phase>/agent-binding.json for the session, the checks-attempt-NN.log files for the gate. See The phase commit for history already written.
  • Records: phax records sync aligns the local records branch. It creates the branch at the remote's tip when it is missing, fast-forwards it when it is behind, and refuses when it has diverged from the remote's or shares no commit with it. --replay recommits the local-only records onto the remote's tip, keeping their trees, messages and authors, and prints the previous tip; a record the remote already holds file for file is left out, and a key the remote also holds with other content is replayed and named. phax never forces the branch. See When the records branch and its remote disagree.
  • Records: phax records sync works for the in-repo destination. It fetches the records branch from publish.remote and no longer says nothing to sync, so it now needs that remote to answer: a script that runs it unconditionally can see it exit 1 on a network or authentication failure.
  • Records: phax records sync --json gains a required branch outcome (created, fast-forwarded, replayed or unchanged) on a cloned or fetched answer, which also gains remoteName. The replayed outcome carries skipped and sharedKeys, and the answer gains replay. A refusal adds one error: RecordsBranchDivergedError (reason, localTip, remoteTip, localOnly, remedy). A script that matches the answer's keys exactly adds them.
  • Records: phax records status --json gains a required branch object (state, localTip, remoteTip), between remote and pending. The text gains a Records branch: line with its remedy; before anything is written or fetched it names phax records sync instead of saying every record is pushed.
  • Records: pending counts only local-only records. On a diverged or unrelated branch, the records the remote already holds are no longer counted.
  • Records: a refused records push names the branch state and the fix. When the branch is behind, diverged or unrelated, the message gives the state and phax records sync or phax records sync --replay, not a plain git push. A record is also written on top of the remote's tip as phax last fetched it, so a lagging branch catches up with the write.
  • Records: a refused records push is always reported. phax publish and phax run print it as a warning, where it was shown only with --verbose.
  • Artifacts: the --json record of artifact new --headless and artifact decide gains a required branchNote beside pushWarning: the note of a record kept on a diverged or unrelated records branch, or null. pushWarning still names only a failed push.

Record format, manifests and versions are unchanged. No migration is needed, but records an earlier version left on a stranded branch (a fresh clone, a second machine) stay pending until you run phax records status, then phax records sync --replay when it names it.

0.34.0

  • 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. 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 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.

Later entries of this release follow.

0.33.0

  • Plan documents, run plans and extraction answers: plan-document, phax-plan and phax-plan-extraction are stamped 0.33.0. A phase's oracle: { dischargedBy, files } key is acceptance: { passedBy, files }, a test line's loop oracle is acceptance, the technique boundary is accepted, and the criterion words acceptance, in-phase and oracle are reserved: no criterion id may be one of them.
  • Records and schema readers: run-status, phase-status, gate-attribution and phase-record-manifest are stamped 0.33.0. A run's hold selection oracle is recorded acceptance, a corrected stop's oracleStep is acceptanceStep, a gate step's surface oracle and a record's verified surface oracle are acceptance, and a record's role is acceptance with passedBy or passing with acceptancePhase, in place of oracle with dischargedBy or discharging with oraclePhase.
  • Provider and run folders: the new acceptance-request replaces oracle-request. The provider reads an acceptance-request stamped 0.33.0 on stdin, with verdict authored or passed and the keys phase, acceptancePhase, base and files, plus acceptanceCommit on passed. phax saves it as checks-attempt-NN.acceptance-request.json. oracle-request is frozen at 0.26.0 and read only: phax writes it nowhere. A provider you wrote reads passed, acceptancePhase and acceptanceCommit in place of discharged, oraclePhase and oracleCommit, and refuses any other document, as the example does: a provider that still reads an oracle-request receives none, and phax refuses a plan with an acceptance phase when no acceptance provider is configured.
  • The Vitest example moved from examples/vitest-oracles/ to examples/vitest-acceptance/. If you copied it, or your phax.json points at node examples/vitest-oracles/main.mjs, change the path to node examples/vitest-acceptance/main.mjs under the key acceptance. It reads only an acceptance-request, names --skip-acceptance in its remedy, and reports findings whose ids start with acceptance- (acceptance-unmarked, acceptance-not-run, acceptance-marker-left, acceptance-changed, acceptance-unparsable).
  • Telemetry: the event oracle.edits.unjudged is acceptance.edits.unjudged. Its console line is phax·acceptance.edits.unjudged, and a failed step named oracle in a span or an event is named acceptance. A dashboard or an alert on the old name stops matching.
  • Overview and records: acceptanceRole and the acceptance rows. phax plans overview --json gives acceptanceRole, whose kind is acceptance (with passedBy), passing (with acceptancePhase) or none, in place of oracleRole. The overview prints acceptance role and phax records explain prints acceptance, where they printed oracle role and oracle; the review handoff's table is ## Acceptance pairs. A script that matches those names changes with them.
  • Skills: rerun phax skills install. The bundled skills (phax-planning, phax-decide-plan, phax-decide-review, phax-review) teach acceptance phases, and a copy made by an earlier release teaches the old names. See After every upgrade.
  • Schema readers: renamed exports in @lbdremy/phax-schemas. parseAcceptanceRequest reads an acceptance-request and an oracle-request at 0.26.0, and toLatestAcceptanceRequest lifts either to the latest acceptance request (discharged becomes passed, oraclePhase becomes acceptancePhase, oracleCommit becomes acceptanceCommit). OracleRequestSchema, parseOracleRequest, toLatestOracleRequest and the types OracleRequest, LatestOracleRequest and OracleRequestShape are gone; OracleRequestV0_26_0Schema stays for the frozen shape. An older @lbdremy/phax-schemas cannot read 0.33.0 documents.
  • Older records read under the new names. A plan sidecar, run plan, status file, gate attribution or record manifest written by an earlier release still reads, lifted to the acceptance names when phax or @lbdremy/phax-schemas reads it. phax records explain lists a record's .acceptance-request.json, and .oracle-request.json for a record written before.
  • Configuration: the oracles key is acceptance. It applies in phax.json, phax.local.json and ~/.phax/config.json. A layer that still carries oracles is refused with <file>: "oracles" is no longer read: write "acceptance". Run phax schema upgrade to refresh the editor schemas.
  • Command line: --hold acceptance and --skip-acceptance. They replace --hold oracle and --skip-oracle, refused with --hold 'oracle' is no longer read: write --hold acceptance and --skip-oracle is no longer read: write --skip-acceptance.
  • Hand-written plans: the header lines and the loop. **Oracle phase:** yes | discharged by phase-NN is **Acceptance phase:** yes | passed by phase-NN, **Oracle files:** is **Acceptance test files:**, and a test line's loop oracle is acceptance. phax plans lint reports the old lines on its acceptance check (formerly oracles) with **Oracle phase:** is no longer read: write **Acceptance phase:** and **Oracle files:** is no longer read: write **Acceptance test files:**, and phax run and phax resume refuse them with exit 11, "Acceptance preflight failed". The old loop is refused with test line <n> uses the loop 'oracle', which is no longer read: write 'acceptance', before any extraction.
  • Run phax artifact migrate-documents once. It restamps every live plan sidecar stamped below 0.33.0, rewrites its plan.md test lines from the loop oracle to acceptance and its header lines to **Acceptance phase:** and **Acceptance test files:**, and commits both files. Spec sidecars are already at their current stamp, 0.32.0, and stay as they are. It refuses a plan whose body was edited by hand since its sidecar was written, and a plan with a test line whose criterion is acceptance, naming the phase and the line: rename that criterion in the spec and the plan, or re-author the plan, then rerun. Until then approve, reopen and decide refuse, naming the command.
  • Runs in flight. A phax-plan.json extracted by 0.32.0 or older is refused by phax resume and phax validate --plan, which name phax archive <run> --force. Finish the run with the release that started it, or archive it and run its plan again. phax ls and phax session-info still show it.

0.32.0

  • Records and schema readers: hand-decision-answer is two formats. spec-hand-decision-answer and plan-hand-decision-answer are each the old variant with a $schema const. hand-decision-answer stays readable at 0.25.0, and phax no longer reads or asks for it.
  • Schema readers: plan-document no longer pairs sourceSpec and completesSpec in its schema. phax checks the pair.
  • Schema readers: every answer's $schema is a const of its 0.32.0 stamp. That covers spec and plan sidecars, the two hand-decision formats, first-reading, interactive-handoff, code-review and phax-plan-extraction. An older @lbdremy/phax-schemas cannot read them, and the package reads a const format only at its own stamps.
  • Schema readers: phax-plan is restamped 0.32.0, its decoding unchanged. An older @lbdremy/phax-schemas cannot read a 0.32.0 run plan.
  • Schema readers: the code-review answer and file are one shape, $schema in place of version.
  • Schema readers: new phax-plan-extraction format, the answer of the plan extraction.
  • Headless authoring answers carry $schema in place of version: 1.
  • Records and schema readers: compliance-review.json is stamped 0.32.0. $schema is a const, and two ledgers are required: unplannedChanges (phaseId, path, note) and unmetPromises (phaseId, promise, note), each possibly empty. A 0.32.0 file without either is refused.
  • Records and schema readers: older compliance reviews read without ledgers. A 0.17.0 or pre-schema review still reads, never with empty ledgers; @lbdremy/phax-schemas gives them Unknown ledgers.
  • Records readers: compliance-review.md is rendered by phax from the answer, with fixed sections (summary, one per phase, unplanned changes, unmet promises, attention points, pointers).
  • phax review-compliance no longer prints Verdict: unknown. A review refused twice fails with exit 1 and writes no file and no records entry; the corrective resume's transcript is compliance-review-retry.session.jsonl.
  • The compliance session is read-only and writes nothing under .phax-context/.
  • Run phax artifact migrate-documents once. It restamps every live spec and plan sidecar below its current shape, their bodies unchanged. Until then approve, reopen and decide refuse, naming the command.

See Structured answers and providers for what each provider now checks.

0.31.0

  • Records readers: security.json is version 3. Every phase's security.json carries a required protectedPaths, between skillEditGrants and providerSkippedForSecurity: null when phax.json declares none, otherwise { globs, enforcement } with enforcement one of enforced, partial or commit-check-only. phax reads version 1 and 2 files as version 3.
  • Records readers: gate-attribution.json is stamped 0.31.0. It carries a required protectedPaths: null, or { result, changes } (a pass with no change, or a fail with at least one { path, kind, entry }). 0.26.0 documents and older still read, as null. An older @lbdremy/phax-schemas cannot read 0.31.0 attributions.

0.30.0

  • Exit 14: the provider could not be reached. A command whose agent cannot reach its provider (a network failure) now exits 14, which is resumable, instead of 5. Treat 14 like 8, but retry as soon as the network is back.
  • Tools that read a phase folder: a failed session's files are archived. When a session starts in a folder that holds an agent-error.log, phax renames it agent-error.failed-NN.log, and the transcript the new session would overwrite <name>.failed-NN.jsonl. See Troubleshooting a failed agent session.
  • Review UIs: review-stop.json and phax ls --json are stamped 0.30.0. A stop's lastClose gains the result network ({ result, at, exitCode: 14, detail }, no step) when its closing turn could not reach the provider; the run stays awaiting_review. 0.26.0 documents still read.
  • Records readers: a failed decision record names its failure. failure (the session's failure kind, answer-refused, artifact-changed or commit-failed) is required on a failed decision record, stamped decision-record-manifest 0.30.0. Records written earlier still read, without it.
  • Mistral Vibe 3.0.3 or later is required. Run uv tool upgrade mistral-vibe. phax checks vibe --version before its first Vibe session and refuses an older Vibe, naming the installed version.
  • phax no longer reads sessions from ~/.vibe. A Vibe turn's answer, session id, usage and transcript come from its stream and from the export.json Vibe writes beside it. VIBE_HOME is no longer a provider store root. phax agent setup mistral-vibe --install-model-aliases still appends the phax aliases to your Vibe config.
  • Secure mode runs Vibe. It is recorded as filesystem jail strong, with command enforcement prefix, so a secure run no longer falls back to Claude Code. Your own config.toml settings (bypass, outside_workdir, tool allowlists) never reach a secure or read-only phax session.
  • Rename a custom accept-edits agent. If ~/.vibe/agents/accept-edits.toml (or $VIBE_HOME/agents/accept-edits.toml) exists, phax refuses every secure and read-only Vibe session (a stop's turns, decide, authoring), naming the file: it would replace the built-in agent phax runs. Rename it, for example to my-accept-edits.toml, and pick it with --agent my-accept-edits in your own sessions.
  • Vibe config under an extra writable root. A secure Vibe session is refused when a root in security.filesystem.allowWrite holds .vibe/ or .agents/, naming it: Vibe loads hooks, agents and tools from such a root. Move that config out, or drop the root. Phase agents on Vibe can no longer write .vibe/ or .agents/ in the worktree either.
  • A turn that ran under another config is refused. After each secure or read-only turn, phax compares the config Vibe ran with (its export.json) with what phax set, and refuses the result on a difference, naming the field in agent-error.log.
  • MCP on Vibe. MCP servers and connectors are off in a session whose mcp.mode is disabled or allowlist: Vibe cannot load only some servers.
  • Records. Vibe usage is { provider, inputTokens, cachedInputTokens, outputTokens, costUsd }, summed over the phase's turn, fix attempts and handoff. Older records still read; an older @lbdremy/phax-schemas cannot read 0.30.0 records.
  • Two Mistral efforts, off and high. The Mistral API serves only none and high reasoning for mistral-medium-3.5, so phax-mistral-medium-3.5-low, -medium and -max are gone from the catalog. phax plans lint and phax run refuse a plan that names one: rewrite it to phax-mistral-medium-3.5-off (effort off) or phax-mistral-medium-3.5-high (effort high). A Claude Sonnet 4.6 phase routed to Vibe runs the high alias, recorded as an upgrade from low or medium and a downgrade from max.
  • ~/.phax/providers.json that still lists them. Every command that reads the provider catalog refuses it, naming each id. Run phax agent setup providers --write to drop them: it also enables every provider whose CLI is installed, so set enabled: false again on any you do not want. Or delete the three entries by hand.
  • The three aliases in your Vibe config. phax agent setup mistral-vibe installs only the off and high aliases, and names the old ones it finds in ~/.vibe/config.toml without touching them. Delete their [[models]] blocks if you like; nothing uses them.

0.28.0

  • A spec or plan option named other. other is now a reserved id, implicit on every question. A live spec or plan that lists an option, or a recommendation, with the id other is refused: an authored answer exits 5, phax artifact approve and decide on the artifact exit 12, and phax artifact status reports a §9 that does not parse. Rename that option (in the sidecar and in the Markdown alike) before upgrading. A Decision: other — … line, and a verdict that chooses other, are accepted.
  • Plan sidecars: testStrategy becomes a list of test lines. Run phax artifact migrate-documents once in each repository with plans under docs/. A sidecar whose every test strategy follows the line format is lifted and committed. One holding prose is refused, naming the phase and the line: rewrite it in the line format in the sidecar and in plan.md alike, or re-author the plan, then run the command again. Until then, phax artifact approve, reopen and decide refuse and name the command. Archived plans are not touched and still read.
  • Hand-written plans. Write each ### Test strategy entry in the line format: <guarantee> — <size> · <technique> · <oracle> · <loop>[ · <criterion>]. phax plans lint reports a departing line as a tests error. phax run, phax plans overlap, phax adjust-plan and phax plans status refuse such a plan before any model call, naming the line and phax plans lint, and phax plans overview exits 1. A phase with no section, or with - (none), has no line.
  • Runs in flight. A phax-plan.json extracted by an older phax is refused by phax resume and phax validate --plan. Finish the run with the release that started it, or archive it with phax archive <run> --force and run its plan again. phax ls and phax session-info still show it.
  • New tests warnings in phax plans lint. Nothing to do. An uncovered acceptance criterion, or a source spec without a readable sidecar, is a warning that never fails lint or refuses a run.

0.27.0

  • Spec sidecars gain a summary on every surface element. Run phax artifact migrate-documents once. It restamps every live spec sidecar, writes a placeholder summary on each element, re-renders §6 and commits; the report counts the summaries left to write per spec. Then write each summary, one sentence in the consumer's words, in the sidecar and on its §6 Summary: line. phax artifact approve and complete refuse (exit 12) a spec until its placeholders are gone, and approve, reopen and decide refuse until the migration has run. Archived specs are not touched.
  • Hand-authored specs: add a Summary: line under each §6 element heading if you want the same layout; phax does not check it.
  • A run whose plan lists an oracle file twice. phax plans lint and phax run now refuse a path repeated on an **Oracle files:** line, and so does phax resume (exit 11, "Oracle preflight failed") for a run an older phax started from such a plan. The refusal names the run's copy of the plan, ~/.phax/runs/<namespace>.<name>/plan.md: remove the repeated path there, check it with phax plans lint on that file, and resume. Or archive the run with phax archive <run> --force and run the fixed plan again.
  • A phase an older phax left committed. Nothing to do. Before 0.27, phax resume went past a phase interrupted between its handoff and its cleanup, or cut short in its handoff turn, and left it committed while later phases ran. phax 0.27 finishes such a phase only when it is the run's last started phase; one with later phases on disk stays as it is, without its record or cleanup, and its worktree stays until the run is archived.

0.26.0

  • Oracle phases need an oracles provider: phax plans lint and phax run refuse an oracle phase without one. Nothing to run; phase records written earlier read with no oracle role.

  • Plan sidecars gain assignmentReason, mode and oracle. modelReason becomes assignmentReason, and every phase gains mode and oracle. Run phax artifact migrate-documents once in each repository with plans under docs/. It renames the key, writes mode: "headless" and oracle: null on every phase, rewrites **Model reason:** lines as **Assignment reason:**, and commits the result. Until then, phax artifact approve, reopen and decide refuse and name the command.

  • Hand-written plans. Rename each **Model reason:** line to **Assignment reason:**. phax plans lint reports the old label as an error, and phax run and phax resume refuse it with exit 11.

  • Runs in flight. A run started by an older phax keeps a phax-plan.json in the older shape, which this phax refuses to resume. Finish the run before upgrading, or archive it with phax archive <run> --force and run its plan again.

  • run-status.json gains a required hold. Scripts that read it find { selection: none } on every run not started with --hold, and runs written by older releases read as none. Nothing to run.

  • New models warnings in phax plans lint. Nothing to do. Lint now compares every phase with the policy default (claude-sonnet-5-5 at medium) where no assignment bound governs it, so a phase at another model or effort with no **Assignment reason:** line gets a warning. Warnings never fail lint or refuse a run. Add a reason line to silence one, or declare assignment bounds.

  • phax ls --json prints a run list. The bare array becomes one run-list document: read .runs[] instead of .[]. currentPhase, gateProfile, recordsPending and archivePath are null when unknown, where they used to be dropped or printed as -, and every run carries stop, its review-stop document or null. Nothing to run.

  • The record manifest gains mode and stop. Records written by released phax read as headless phases that never stopped. Records written by 0.26.0 development builds before this change no longer read: phax records explain on a commit of such a run fails and names the malformed manifest, and phax records list skips the record.

  • security.json moves to version 2. It gains a required stop. A version-1 file reads as a phase that never stopped; a close rewrites it as version 2. A run started by a 0.26.0 development build with a closed stop or an interactive-session.json does not resume.

  • Closing a stop needs a git identity (user.name and user.email) in the main checkout; a close without one is refused with exit 1.

None of these four changes needs a command.

0.25.0

  • Spec and plan sidecars move to their current shapes. Run phax artifact migrate-documents once in each repository with specs or plans under docs/. It rewrites every live sidecar below its current shape and commits the result. Until then, phax artifact approve, reopen and decide refuse and name the command.
  • Cost ledgers for older runs. Nothing to do. phax plans estimate, the only reader of cost ledgers, writes one for every kept run that has none before it estimates. phax cost backfill does the same by hand.

0.19.0

  • Approval ledgers become one record file per artifact. Run phax artifact migrate-approvals once on a clean tree in each repository that still has docs/plans/approvals.json or docs/specs/approvals.json. It commits the split by itself. Until then, approve, plans status, artifact status and phax run refuse with exit 12 and name the command.