phax 0.34.0
Changes since 0.33.0: v0.33.0...v0.34.0.
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
- Gate steps accept
partial. An optionalpartialobject with afilesand/or adiffcommand, 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 validateandphax run --dry-runlist the prefixes each profile's partial commands allow (the--jsonanswers gainpartialPrefixes). See Partial commands. Records readers:security.jsonagentCommands[].sourcegains 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 extraandphax ls extraexit 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 wroteUnexpected 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-schemaprints the contracts of the commands answering through the runner, with the program's exit codes and the shapes of its message lines.--json-schemaafter a command that does not answer through the runner is refused with exit 64. - Run list:
phax ls --jsonrows gain the requiredpullRequest. It is the URL of the pull request phax opened for the run, ornullwhen it opened none or the run's publication record cannot be read.run-listis stamped 0.34.0; a list stamped 0.26.0 or 0.30.0 is still parsed by@lbdremy/phax-schemas(parseRunList), andtoLatestRunListgives each of its rowspullRequest: null. A script that matches the row's keys exactly adds the new one. - Schemas package:
artifact-status,plan-overviewandplan-lintare new formats at 0.34.0.@lbdremy/phax-schemasexportsparseArtifactStatus,parsePlanOverviewandparsePlanLint, with their schemas,toLatestfunctions and JSON Schemas, andparseDocumentidentifies each. They describe the answers ofphax artifact status --json,phax plans overview --jsonandphax plans lint --json, which phax never saves. - Plans:
phax plans overview --jsonandphax plans lint --jsonanswer versioned documents. Each prints aplan-overvieworplan-lintdocument 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 withparsePlanOverviewandparsePlanLint.plans lintstill exits 1 when a finding is an error, with or without--json. - Artifacts:
phax artifact status --jsonis new. It prints anartifact-statusdocument: the path, kind, status, legal transitions, approval, authoring and questions the text reports. Without--jsonthe text is unchanged. - Command line:
artifact status,plans overview,plans lintandlsanswer 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 anoteline, a failure anerrorline, a refused call arefusedline.--json-schemaafter 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>andphax <group> help <command>print it too. - Plans:
plans status,plans overlapandplans estimateanswer through the runner. Their--jsonobjects are unchanged exceptplans status --apply, which gainswrittenafterapplied, the flipped plans' records. Under--jsontheir warnings are note lines.phax plansalone and--json-schemafollow the runner. - Command line:
validate,session-info,path,brief,architecture,completionsandreportanswer through the runner. Under--jsoneach answers one JSON object and message lines on stderr, and--json-schemaafter each prints its contracts. Their text and codes are unchanged;phax pathprints the same bytes.phax completionsprints 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 statusandskills installanswer--json. The writing ones list the files they wrote as written-file records (cost backfillthe cost-ledger.json files, by absolute path;schema upgradethe two schema files;skills installeach installed file), andskills installnames the files already installed when it fails part way. Without--jsoneach prints the same text with the same codes. Thecost,schema,securityandskillsgroups called alone are refused with exit 64 and their help; barephax security, which ran the status report and exited 0, now does the same, so callphax security status.--verboseand--traceonsecurityandsecurity statusare the program's options, accepted anywhere in the call as before. - Command line: the
recordscommands answer--json.records initprompts, sorecords init --jsonis refused with exit 64 before anything is asked, although its help lists--jsonas every command's does.records explain --jsoncarries the text of each artifact its flags ask for (--prompt,--diff,--transcript,--gates,--briefs),nullwhen the record does not carry it. Without--jsonthe text, error sentences and exit codes are unchanged, andphax recordsalone is refused with exit 64 and its help. - Artifacts: the transitions,
artifact schemaand the migrations answer through the runner. Under--json,artifact approve,reopen,stale,abandonandcompleteanswer 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-approvalsandartifact migrate-documentsanswer their report with the record files, or the sidecars and bodies, they wrote.approve --jsonandreopen --jsonwithout--byare refused with exit 64 before anything is written, because a terminal standing for the operator is a person's gesture; the refusal readserror: --json: artifact approve prompts; pass --by.artifact schemaprints the same bytes in both modes. Under--json,migrate-approvals' orphan-record warnings are note lines on stderr. An unknownartifact schemakind now exits 64, with the runner's text. - Artifacts:
artifact decideandartifact new spec|plananswer 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 aserror: 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 writtenverdicts.0.note. Before,decideanswered these with exit 12 andartifact newwaited on a terminal stdin.--briefgiven without--headlessis now read, and refused when unreadable.decide --jsonwithout--verdicts, or without--by, is refused with exit 64 before anything starts, where it exited 12 with--json needs --verdicts; the refusal readserror: --json: artifact decide prompts; pass --verdicts and --by.decide --verdicts --jsonprints its object two-space indented, not on one line, withwritten(the files its commit holds) andrecord(the decision record's outcome) after its twelve keys, and itsrecordline no longer reaches stderr.artifact new --jsonanswers the path, kind and lineage of a blank skeleton, or with--headlessthe path, sidecar, commit and authoring record. Under--jsonthe 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 artifactandphax artifact newalone are refused with exit 64 and their help on stderr, asphax agentandphax agent setupare: the help puts a group's long help before its commands, listshelp [command]last, and ends withRun `phax --help` for usage..phax artifact archivekeeps its sentence but exits 64, not 1, follows it withRun `phax --help` for usage., and answers--helpwith that refusal, not with help. The help ofdecideandartifact new spec|planlists--verdictsand--briefafter the other options, beside astdin: JSONorstdin: textline. Without--jsonboth commands print the same text with the same codes otherwise. - Agent:
agent models,resolve,probe,setup providersandsetup mistral-vibeanswer through the runner. Each answers--jsonwith one indented object (modelsthe routing table and priority,probeeach provider's availability and whether config enables it, the setup commands what they did).agent resolve --jsonis 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--effortor a failed setup is oneerrorline on stderr. Without--jsonthe text, error sentences and exit codes are unchanged.phax agentandphax agent setupalone are refused with exit 64 and their help. - Root commands:
init,unlock,archiveandpruneanswer--json.init --yes --jsonanswers 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 --jsonwithout--yesis refused with exit 64 before anything is asked or written, even off a terminal, namingpass --yes.unlock --jsonanswers{ shortName, removed, qualifiedName },removedone ofstale(the holder's process is gone),expired(the lock outlived its lifetime),forcedornone.archive --jsonanswers{ qualifiedName, crossProject }.prune --jsonkeeps its object byte for byte, but its preview lines (Prune from namespace …) are now note lines on stderr, andprune --jsonwithout--yesor--dry-runis 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 oneerrorline under--json. Without--jsonthe text, error sentences and exit codes are unchanged, andprunekeeps-ybeside--yes. - Review:
review-handoff,publish-pr,review-complianceandreview-codeanswer through the runner. Under--jsoneach answers one object: the run's qualified name and whether it is another project's, thenreviewHandoffandwrittenforreview-handoff,pullRequest(the URL, ornullwhen the branch was pushed without opening one) forpublish-pr,verdict,review,notice,warningsandwrittenforreview-compliance, and forreview-code --headlessfindings(a count and its summary),findingsPath,fixHint,notice,warningsandwritten.review-handofflists the reconciliation,review-handoff.mdandfinal-report.mdit regenerated,review-complianceits Markdown and JSON, andreview-code --headlesscode-review.jsonandcode-review-session.json, each as a written-file record. The records and push warnings are note lines on stderr under--json.review-code --jsonwithout--headlessis 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--jsonthe text, streams and codes are the same. Inphax --help,publish-prandreview-complianceshow[options]in their usage, since they now take--json. - Sessions:
enter,enter-phase,shell,openandadjust-plananswer through the runner. Each hands the terminal to a session, a shell or an editor, so under--jsoneach is refused with exit 64 and onerefusedline before anything is read or started, naming the command that answers instead:phax session-info <run> --jsonforenterandenter-phase,phax path <run> --jsonforshellandopen, and foradjust-planthat it runs from a terminal without--json. Before,--jsonwas an unknown option. Without--jsoneach 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.shellstarts$SHELL(orbash) through the session port, andadjust-planreads its plan through the file system port. Inphax --help,enter,shellandopenshow[options]beside their name. - Runs:
run,resumeandreset-phaseanswer through the runner. Under--jsoneach answers one object naming its outcome:runandresumeanswer{ qualifiedName, crossProject, outcome }, the outcome adry-run(the report--dry-runprints), apreview(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) orpaused;reset-phaseanswers itspreviewor itsreset. Every progress line (Run: <run>,using cached extraction,would resume …, the extract warnings, the--verboseevents) 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 outcomepausednames its reason, phase, resume command, resume-instructions path,resetAtwhen 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 oneerrorline with today's code; a held run lock isLockConflictErrorwith exit 7 and fieldsrun(the short name) andpid.resumeandreset-phasewithout--yesanswer their preview under--jsonand take no lock. Every exit code stays.resume --skip-oraclekeeps its sentence but exits 64, not 1, and follows it withRun `phax --help` for usage..resumeandreset-phaseno longer list their own--verboseand--trace, in their--helpor in the usage spec: they are the program's options, accepted anywhere in the call as before. Under--json, auto-publish's--verboselines (Publishing branch to …,Pull request created: …) and the run-end reviews'[phax] …lines are note lines too, as arepublish-pr --verbose's. An interruptedrunorresume(SIGINT or SIGTERM) still saves the run and exits 130; under--jsonits sentence is note lines. A run of short flags is read as one word:resume x -yvis refused naming-yv, where it named-v. The help ofresumeno longer says it prompts: without--yesit prints where it would resume and exits 0. Without--jsonthe text, streams and codes are unchanged;-ystays beside--yes, andrun --holdkeeps its optional value. - Command line: every command answers through the runner.
phax --json-schemalists every command, and--json-schemaafter any command prints its contracts; it is no longer refused after a command.phax help <command>answers that command's help, andphax 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 declaresubcommand_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--usageand--usage-formatbefore--verboseand--trace. A script that reads the file with theusageCLI sees the same commands, arguments and flags.--version,-Vand--usageprint as before when called alone. Root help gains a--json-schemaline under Options;path,brief,architectureandcompletionsshow[options]besideenter,shell,open,publish-prandreview-compliance, andsecurityno longer does.--helpand--versionare 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. Barephax, orphaxwith only program options (phax --verbose), writes the root help on stderr and exits 64, not 1, ending withRun `phax --help` for usage..phax <group> help [command]answers likephax <group> [command] --help. The phase guard refuses inside a phase worktree with today's sentence and exit 1, or onerefusedline under--json, while--help,-hand--json-schemastill answer; it now runs before the call is read, so a malformed call there (phax runwithout--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.mdand.claude/settings.local.jsonare left out too. Your ownpermissions.denyrules 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.jsonor managed settings. If you authenticate through user settings (anapiKeyHelper, anenvblock, Bedrock or Vertex set there), runclaude /loginor setANTHROPIC_API_KEYin the environment phax runs in, or, for Bedrock, Vertex or a gateway, exportCLAUDE_CODE_USE_BEDROCK,CLAUDE_CODE_USE_VERTEXorANTHROPIC_BASE_URLand its credentials there; otherwise the phase fails withnot-authenticatedand names the remedies. If another credential is available (aclaude /loginsession,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 requireduserConfig(excludedfor claude-code,loadedfor codex-cli and mistral-vibe), andphase-record-manifestanddecision-record-manifestare stamped 0.34.0; earlier records read asloaded, andexcludedcovers a phase's headless sessions only. See What a Claude Code session loads.
Later entries of this release follow.
What's new
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 commandNow:
Before
-
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 --jsonNow:
Before
-
phax --json-schemaprints every command's input, output and error contracts, andphax <command> --json-schemaprints one command's, without running anything.--json-schemaNow:
-
phax artifact status <path> --jsonanswers with a versioned document: the artifact's status, legal transitions, approval, authoring and open questions.phax artifact status --jsonNow:
Before
-
phax plans overview <plan> --jsonnow answers with a versionedplan-overviewdocument a reader can validate.phax plans overview --jsonNow:
Before
-
phax plans lint <plan> --jsonanswers with a versionedplan-lintdocument, and still exits 1 when a finding is an error.phax plans lint --jsonNow:
Before
-
Each run in
phax ls --jsonnow carries the URL of the pull request phax opened for it.phax ls --jsonNow:
Before
-
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 codesNow:
Before
-
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 stdinNow:
Before
-
The usage spec lists
--jsonon every command and is otherwise unchanged: same commands, arguments, options, help and completions.phax.usage.kdlNow:
Before
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.jsonNow:
Before
-
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 sessionNow:
Before
Fixes
- fix(cli): review of command-contracts plan 2 (095c1af)
- fix(cli): keep --json clean on run, resume and publish-pr; restore group help (2324e93)
- fix(prompt): test the gate section per provider; say so when no step runs (69a2550)
- fix(session): name the cloud remedies; keep a hook's "Not logged in" off Claude's login (8fc1cd5)
- fix(gate): refuse quotes, escapes and globs in partial commands; list what they allow (c9d51dd)