Checkwright v0.36.0
2026-11-14
Checkwright catches reproducible classes of unsupported claims in coding-agent delivery before merge, and this release installs one session-hook arm for you rather than a repository, lets any hook run as a trial that enforces nothing, and runs the turn-end liveness hook outside any repository.
In brief
| Section | Entries | Who acts |
|---|---|---|
| New and tightened gates | 6 | everyone: these may red on upgrade |
| Knob changes | 5 | if you set knobs |
| Gate-authoring changes | 0 | if you write or shadow gates |
| Platforms | 0 | if you install on a listed platform |
| Behavior changes | 13 | if you depend on a listed behavior |
- You can now install one session-hook arm for yourself with
init --user <arm>, from any directory, without vendoring a kit into a repository. - Any hook can run as a trial first: it logs what it would have decided and blocks nothing.
- The turn-end liveness hook now guards turn ends fired outside a repository, and each tree-free hook arm has its own page with a generated knob table.
- You must act on one red: register the new gates in your
gates.listor declare them unregistered. Both read clean until you set their knobs.
New and tightened gates
check-settings-pathsandcheck-plugin-parity— red a hook registration whose tokens after the member leave--hook <member> [--observe] [--binding <name>] [--knobs <absolute file>], which they read as clean before; the arm now refuses such a registration at run time. Fix the misspelled or misordered token.check-settings-paths— reds a hook registration whose--knobsnames a relative path, which it read as clean before; the arm declines such a registration on every firing. Write the file’s absolute path.check-hook-knobs-fresh— new, off untilGATE_SDK_HOOK_PAGES_DIRis set: holds each hook-arm page’s knob block to its member’s knob table, and reds a page naming no member and a tree-free member with no page. Nothing to do unless you publish a page per hook arm; register it with the knob, then run--emit hook-knobs --write.check-gate-substrate-parity— assertion I reds oncheck-hook-knobs-fresh, a new gate-sdk gate the binary carries, until yourgates.listregisters it or declares# unregistered: check-hook-knobs-fresh — <reason>. It reads clean whileGATE_SDK_HOOK_PAGES_DIRis empty, so register it.check-docs-since— new, off untilSITE_KIT_SINCE_PAGESis set: holds each declared page to asince:front-matter key,unreleasedorvX.Y.Z. WithSITE_KIT_RELEASE_NOTES_DIRset, it also reds a page sayingunreleasedwhile a release note is untagged. Nothing to do unless your pages state a release status; register it with the knobs, then run--emit docs-since --writein each note-authoring commit.check-gate-substrate-parity— assertion I reds oncheck-docs-since, a new site-kit gate the binary carries, until yourgates.listregisters it or declares# unregistered: check-docs-since — <reason>. It reads clean whileSITE_KIT_SINCE_PAGESis empty, so register it.
Knob changes
GATE_SDK_HOOK_OBSERVE_LOG— new: the log an observed hook appends its would-be decision to, default<GATE_SDK_TMP_DIR>/hook-observe.log. Nothing to do unless you register a hook under--observe; outside any repository, set it absolute.GATE_SDK_HOOK_PAGES_DIR— new: a word list of the directories whose hook-arm pages--emit hook-knobswrites andcheck-hook-knobs-freshholds, default empty, which governs no page. Nothing to do unless you publish a page per hook arm.SITE_KIT_SINCE_PAGES— new: the page globscheck-docs-sinceand--emit docs-sincegovern, default empty, which governs no page. Nothing to do unless your pages state a release status.SITE_KIT_RELEASE_NOTES_DIR— new: the directory of release notes thesince:note rule and its rewrite read for the untagged note, default empty, which turns both off. Nothing to do unless you setSITE_KIT_SINCE_PAGES.DELEGATION_KIT_STOP_WAIT— new: the turn-end hook’s waiting policy,refuseorresume-wait, whose default member is the hook’s behavior unchanged. Any other value fails the knob table at exit 2 and makes the hook decline with an operator notice. Nothing to do; bindresume-waitonly where your harness gives a session a new turn when its own background task completes.
Gate-authoring changes
None.
Platforms
None.
Behavior changes
--hook— takes--observeafter the member, under which any member’s would-be decision is logged toGATE_SDK_HOOK_OBSERVE_LOGand the call proceeds at exit 0 with nothing said; an observedshell-guardgrants nothing, so a trial may show permission prompts. It refuses at exit 2 a trailing token outside--hook <member> [--observe] [--binding <name>] [--knobs <absolute file>], where it ignored the token and ran the member enforcing. Fix any such registration; add--observeto trial a hook before it enforces.subagent-stop-liveness— under theresume-waitmember ofDELEGATION_KIT_STOP_WAIT, allows a turn end over a live producer while a running shell task of the session’s own will resume it, and refuses one whose launch record declares a hand-back. The launch record takes an optional third field,handoff=<path>, whichcheck-producer-livenessand the shell guard read as the same live producer where they read such a record as corrupt or skipped it. Its log line gains await=field beforedecision=. Nothing to do under the default.--install-hooks— refuses at exit 2, placing nothing, acore.hooksPaththat is not UTF-8 at the effective or the local scope, or a set value whose prior hooks directory resolves to a path that is not UTF-8, as through a link, where it chained that directory as one holding no hook and advised a restore line naming another value. It also keeps a line break the value ends in, so the line-break refusal now meets it, where it trimmed the break and chained a different directory. Rename the directory or respell the value, then re-run.--emit env-probe— refuses at exit 2 a profile file whose markers do not pair around an unchanged block, where it reportedunchangedat exit 0; the file is untouched either way. Repair the markers, then re-run.init,updateanduninstall— refuse at exit 1 before any write where git has no identity to commit with, the author or the committer, where they wrote and staged everything first and left git’s failed commit as the only account. A run that would commit nothing refuses too, and--no-commit, a personal install and--dry-runwaive the check. Setuser.nameanduser.email, or pass--no-commitand commit yourself.--hook subagent-stop-liveness— runs outside any repository when its scratch dir and log are absolute, and otherwise declines naming the knob, where it declined with the no-tree notice. SetGATE_SDK_TMP_DIRandDELEGATION_KIT_STOP_LOGabsolute to guard turn ends fired outside a repository.--install-hooksanduninstall— refuse at exit 2 a common git directory that is not UTF-8, where--install-hooksplaced hooks under a replaced spelling. Rename the directory, then re-run.--hook <member> --knobs <absolute file>— new: runs a tree-free member under the named knob file as its only file layer, inside a repository or not, entering no toplevel and reading no project knob file or<KIT>_KNOB_FILE. A file it cannot use declines at exit 0 naming the file and line; the token on a member that reads a repository is refused at exit 2. Nothing to do unless you register a hook for a user rather than a repository.init --user <arm>,update --useranduninstall --user— new: install one session-hook arm for the user rather than a repository, from any directory and with no repository precondition. The gate binary and the arm’s knob file go in the per-user data dir ($XDG_DATA_HOME/checkwright, else~/.local/share/checkwright;%LOCALAPPDATA%\checkwrighton Windows), recorded inuser.lockthere, and the arm’s scratch and log in the per-user state dir.init --userprints asettings:block carrying the harness entry,--hook <arm> --knobs <knob file>under the absolute binary, for you to merge into your user settings; nothing writes it.update --userre-places every recorded arm and reprints each block;uninstall --userremoves the binary and every knob file you have not edited, keeps the state dir, and reminds you to remove the entry. Nothing to do unless you want one arm without vendoring a kit.- The gate binary — refuses an argument that is not UTF-8 at exit 2, naming its position, where it panicked.
uninstall— prints the line restoringcore.hooksPathon every exit after it removes the placed hooks directory, a failed commit included, where a refusal there lost it.init,uninstallandprove— read a personal install as personal when its exclude file also holds a line that is not UTF-8, where they read it as a tracked install:initran the committing install anduninstallleft the block.--usage-poll— writes a snapshot whose five-hour pair is empty beside the weekly pair when the source carries no five-hour window, which the verdict judges on the weekly axis, where it refused the payload as unparseable and left the snapshot to go STALE. A payload with neither window, or with half of the five-hour one, still refuses. Nothing to do.
Upgrading
Replace the vendored kit directories wholesale at v0.36.0, or run checkwright update. Regenerate your generated artifacts; each freshness gate that reds prints its own regen command. Then run the full battery.
The allowed reds. Every gate under New and tightened gates above may red, and its bullet names what clears it. On a tree that sets none of the new knobs, expect only check-gate-substrate-parity’s assertion I, naming check-hook-knobs-fresh and check-docs-since: add both to your gates.list, where they read clean until you set their knobs, or declare each # unregistered: <gate> — <reason>. check-settings-paths and check-plugin-parity red only a hook registration with a misordered token or a relative --knobs path; fix the registration.
The behavior changes above are declared for reading, not a mechanical scan. If a gate reds that this note does not name, the upgrade smoke was supposed to catch it first — open an issue, because that is a defect in the release rather than work for you.