How it holds
The guarantee lives in git hooks, which git runs from its own state after the shell is done. So it travels with the repository. A merge that brings feature code to the trunk without closing a spec or recording a chore is refused in your clone.
The forge check asks the same questions on the pull request. Since 2.12.0 the audit's codes name every verdict it prints, and a git call the audit depends on that fails is refused by name, so an audit that read nothing never passes as clean.
Since 2.13.0 the commit layer reads the same way: a commit-time hook whose git call fails refuses by name and never reads the failure as an empty answer. You adopt it in levels, in this order:
| Level | Who needs it | What it refuses |
|---|---|---|
The git hooks (pre-commit, pre-merge-commit, pre-push) |
Everyone, in every clone | A commit, a merge or a push whose history skipped a spec |
| The forge check | A team | The pull request's merge, where the trunk requires the check beside a review |
| The session layer | Optional | Nothing: it warns the agent, and its one deny nudges |
| Custody | Whoever approves specs | A build whose spec approval is missing or drifted (a key, or the forge's review) |
Since 2.14.0 the long commands, /setlist:checkpoint, /setlist:upgrade and /setlist:validate, keep each SKILL.md under 20,000 bytes and read their rarely needed sections from references/ files beside it, so the steps a flip and a close depend on survive a long session's compaction.
The upgrade to 2.14.0 names every edit an instance receives and migrates nothing in your own files: the new header lines and settings keys are read only where present. The upgrade to 2.15.0 delivers the changed hooks and the trunk audit and migrates nothing either.
The plugin version it records is what puts the two push refusals in force, so a branch cut before the upgrade is not asked until it merges the trunk. Since 2.12.0 every instance carries a verify skill. Claude Code asks for a skill of that name before a commit, and this one runs your gate command and reports what it printed.
It persuades the agent and refuses nothing, and the git hooks refuse whether or not it ran. Since 2.13.0 the bypass deny blocks a command when it times out or cannot start (Claude Code 2.1.295 and later), and the forge check reads no credential: the stamped workflow asks the forge and hands it the answers.
What the arrows assert: every refusal between unreviewed work and a shared trunk comes from .githooks/pre-commit, .githooks/pre-merge-commit, .githooks/pre-push through .claude/hooks/trunk-audit.sh, or .claude/hooks/forge-check.sh, and all four source one set of predicates in .githooks/setlist-hook-lib.sh.
The scope hook's arrow is a read that ends in advice and never in a refusal. The trunk audit runs at every push and on every pull request. It reads history rather than commands, so it sees work that arrived through a renamed branch, a cherry-pick or the forge's merge button.
It walks from the trunk audit's baseline, which /setlist:upgrade records in .claude/sdd.json where an instance's history predates its hooks. Refused merges complete the way the message says, at push as at commit. The armed check reports at session start when core.hooksPath or merge.ff is off, and the stamp refuses a bad answer by name.
On a checked-out branch without .claude/sdd.json, pre-commit and pre-merge-commit exit in silence, and pre-push reads the pushed commits' own. A branch without .githooks/ runs no hook. Another hook manager (husky, lefthook, pre-commit) is chained, its hook running beside Setlist's verdict.
An octopus onto the trunk is refused by name. The hooks and the audit read history with replace refs ignored, so they judge the commits a push sends, and a failed read of HEAD is refused by name at commit and merge time (2.14.0).
On a first push to an empty remote, pre-push audits the trunk the instance declares and names every other pushed branch it did not audit (2.14.0). A committer who crafts merges to evade the audit can, and those routes are the Known limitations.
Does it work? Every release passes a dogfood gate: a fresh agent given only this repository takes a small application from an empty directory to a merged, twice-QA-passed feature. The suite behind the CI badge drives every hook through fixture repositories on Linux and macOS at every push, and its platform smoke on Windows.