Receipts is my Python Claude Code plugin for making completion claims checkable: I want evidence from tools that actually ran, not an agent’s impression of its own work.
I tuned Receipts against real Claude Code sessions with a replay harness, then checked its behavior with live hooks. The goal is to make completion claims answerable to recorded verification without overstating what the tool can establish.
I replayed 1,356 real turn-ending messages from 119 Claude Code sessions through the actual gate, rebuilding a ledger from each transcript.
After tuning, the gate fired on 11.9% of all turns, down from 22.7% in the first cut.
I limited generic failure sniffing to test, build and lint; this cut false failures from 76 to 21.
After tuning, 38% of all firings were turns like “poora scan ho gaya”—claims about reading, not code.
The replay left 91 turns that edited source after the last green run and then said they were done.
The replay exposed several practical classification problems. I now classify commands by the program being run, after splitting simple commands and unwrapping common launchers, rather than matching against the whole command line. Staleness is limited to relevant source changes; formatter write modes count as changes, git push is an action rather than verification, and test scripts are recognized by name without mistaking the shell builtin test for a test run.
Live validation caught timing and environment cases replay could not establish. At Stop time, the transcript may still end with an earlier reply, so I use last_assistant_message from the Stop payload. I also added recognition for python3 -m unittest and allow source edits under /private/tmp to make a verification stale. When multiple sentences rest on the same cause, the gate groups them into one report.
I validated 34 end-to-end cases that drive hooks/gate.py and hooks/record.py as real subprocesses over stdin.
In the inert-test live run, the recorded result was verdict: pass, 1 passed; /prove recorded INERT and the gate refused the next completion claim.
The Stop hook can refuse a stop with a reason returned to the model, and stop_hook_active guards against loops. PostToolUse payloads have no exit code, so Receipts parses command output with framework-specific parsers; unparseable results are unknown, never a pass. Receipts appends to its own ledger and leaves tool output unchanged. Its inert check uses a detached worktree, links existing dependency directories, runs tests there, and removes the worktree without touching my working tree.
Receipts requires python3 on PATH; no dependencies — standard library only.
claude plugin marketplace add namanjain24-sudo/receipts
claude plugin install receipts@receipts
python3 tests/test_receipts.py
python3 tests/replay.py
The replay script evaluates the gate against my own transcripts, rebuilding a ledger and running the real gate over every turn; --samples 10 prints caught cases for review. Configuration is optional in .claude/receipts.json, with enabled, locales, and checks fields. Removing a check name disables that rule.
I can add hinglish to locales for romanised Hindi claim phrasing.
Receipts is not tamper-proof: an agent with shell access could modify the ledger, and its threat model is an optimistic agent rather than an adversarial one. It does not read code to judge test quality; /prove checks whether a test discriminates by running it against a detached worktree. Claim detection is pattern-based, so sufficiently creative phrasing may evade it, while hedges such as “tests should pass” are deliberately exempt. Exotic test runners become unknown, which blocks rather than passes. The inert check can return INCONCLUSIVE when there is no git repository or uncommitted work, or when dependencies cannot be installed.
Because PostToolUse payloads have no exit code, Receipts parses output with framework-specific parsers for pytest, jest/vitest, go test, cargo, mocha and tsc.
Among its 34 end-to-end cases, two build actual git repositories—one with a discriminating test and one with an inert test—to check that the checker tells them apart.
The project tagline is “Don't tell me it's done. Show me the receipt.”
Receipts is a repo on @namanjain24-sudo's GitHub; started 2026-10, last updated 2026-10.
Hooks record proof from real tool output; the model never writes its own report card.
Across 547 real agent jobs, “done, tests pass” was reported 265 times.
When those claims were rerun in a clean sandbox, about 1 in 11 was false.
In 8 cases the test still passed after the production change was reverted.
SonarSource's 2026 survey says 96% of developers don't fully trust AI-written code, while only 48% verify it before committing.
On SessionStart, Receipts drops ledgers from sessions older than a week.
On PostToolUse, Receipts writes an append-only ledger of Bash, Edit and Write calls, including pass/fail verdict parsed from real output.
At Stop, Receipts reads the final message, finds completion claims, and refuses the stop when the ledger does not back them.
The /prove command asks whether a test would have passed anyway.
- Receipts addresses five failure shapes: a failed check hidden by the summary; a stale check after later code changes; a subagent failure dropped by the parent; no check of the relevant kind; and an inert test that passes with or without the change.
For failed checks, Receipts uses this rule: reading the real output, not the model's account of it.
To detect stale checks, mutations and checks share one sequence in the ledger.
To catch failures a parent might omit, Receipts scans subagent results in the transcript.
Receipts maps each claim kind to a required evidence family: claim kind → required evidence family.
The /prove command re-runs a test at HEAD in a throwaway worktree: /prove: re-runs it at HEAD in a throwaway worktree.
If the test passes at HEAD too, it is green with or without the change, so it does not prove the change works.
In the stale-check example, pytest -q reported 120 passed, and src/parser.py changed after it: pytest -q (120 passed), and src/parser.py changed after it.
The suggested remedy is to run the project's test command and read the summary line: run the project's test command and read the summary line.
If a claim is unsupported, saying it is unverified is acceptable: Saying it is unverified is a fine answer.
The example /prove run uses python3 -m unittest -q against HEAD in a throwaway worktree and labels the result INERT when it passes at HEAD too.
The README does not specify the repository URL, installation or configuration instructions, ledger storage format, exact claim-detection rules, evidence-family command mapping, or how /prove chooses a test command and isolates the production change. It also does not give the underlying sources or methodology for the reported job figures or survey.