Claude says "done, tests pass". This repo checks that claim before you ever see it. When Claude stops, a Stop hook runs gates and acceptance tests Claude cannot read or change. A RED sends the reason back, and Claude fixes its own work. After three REDs the ticket goes to a human.
It is plain Claude Code, on a tiny checkout cart in Bun and TypeScript: CLAUDE.md, one path-scoped
rule, permission rules, three hooks, one skill, one reviewer subagent, and a harness of gates and
holdout tests. Free to clone and try.
You need git, bun and Claude Code. tmux is optional.
git clone https://github.com/learnwithparam/claude-verification-harness
cd claude-verification-harness
bash harness/doctor.shOpening Claude Code in this folder runs the hooks in .claude/hooks/ on your machine, after you trust
the folder. Read them first: they are short. Run claude here once and accept the trust dialog before
bash harness/try.sh: until then Claude Code ignores the repo's allow rules. docs/run-2026-10-07.md shows what
five rehearsal runs did.
| Piece | What it does |
|---|---|
CLAUDE.md |
Guidance loaded every session: how to run things. Claude may ignore it. |
.claude/rules/money.md |
Guidance loaded only when Claude opens a file in src/: amounts stay whole cents. |
.claude/settings.json |
Enforced: permission rules and the three hooks. A Read deny covers the Read tool only. |
.claude/hooks/guard.ts |
PreToolUse. Denies reading the holdout, editing tests or the harness, rewriting src/ with a script (the after-edit gates would miss it), and git commands that read other commits. A Read deny rule alone does not stop cat in Bash. |
.claude/hooks/after-edit.ts |
PostToolUse. Runs the gates after each edit to src/ and hands failures back as context. |
.claude/hooks/stop.ts |
Stop. Runs harness/verify.sh. RED exits 2 with the reason; the third RED hands over to a human. |
.claude/skills/ticket |
/ticket 01 starts a ticket. Only you can run it. |
.claude/agents/reviewer.md |
A read-only subagent: after GREEN it reads the ticket and the diff for what no test checks. |
harness/gates.sh |
One line per gate: whole cents, no .only, visible tests, nothing tampered with. |
holdout/ |
The product owner's acceptance tests. Claude is graded on them and never shown them. |
tickets/ |
Three small tickets, written as loosely as real ones. |
bash harness/verify.sh # GREEN on main
bash harness/try.sh 01 # Claude on ticket 01, in its own worktree with no holdout/ on diskClaude implements the ticket, the visible tests pass, and it says done. The Stop hook answers with the name of an acceptance test it failed, and Claude goes back to work without you typing anything.
Pick a rule your team keeps repeating in review. Add one gate line to harness/gates.sh, then
bash harness/verify.sh. Every Claude run from now on meets it.
bash harness/try.sh 02 # or 03
bash harness/tmux.sh # three Claudes on three tickets, plus a pane of verdicts
bash harness/reset.sh # clean up worktrees, branches and verdictsThe Makefile wraps these same scripts (make try T=02) if you prefer make; nothing needs it.
Then copy .claude/ and harness/ into your own repo and change gates.sh to your stack.
- The holdout sits in the repo, behind hooks, so you can read it. In production it lives where the agent cannot reach at all: CI or a separate repo.
- The hooks and the harness live in the same working tree as Claude. The guard is a denylist and a determined agent can get round it. In production the verdict that counts runs in CI, from a commit the agent did not write.
bun test tests && bun test --timeout 30000 ./harness/harness.test.tstests the harness itself, and runs in CI on every push.