Show HN: Makoto the Claude Code plugin that keeps the AI honest Makoto is a plugin for Claude Code that intercepts the AI agent's tool calls and blocks those that fabricate evidence, weaken verifiers, or make unsubstantiated claims, enforcing honesty by checking the agent's actions against its own statements. An integrity hook for Claude Code that watches the agent's own tool calls and blocks the ones that fake a check. When Claude says it did something ran the tests, cited a paper, committed the fix, verified the certificate , makoto holds that word against its record. If the deed isn't there, or the verification was quietly disabled, makoto blocks the tool call or the end-of-turn and hands the agent a one-line correction to retry against. It judges the agent against its own utterances and record, never the world's truth. It holds no facts "France doesn't exist to it" ; it only checks that a claimed word is kept, whole, and honored in deed. A word it lets through becomes spendable: trustworthy tender a reviewer or another agent can accept without re-deriving it. makoto fires on mechanical hook events: every PreToolUse , PostToolUse , Stop , and advisory-only; see ConfigChange watch configchange-watch-advisory--evidence-gated-blocking below ConfigChange . It blocks exit 2, which Claude Code treats as block-and-retry on 22 pre-checks across five families and 14 end-of-turn gates plus gate.stale establisher and gate.undeclared falsifiable , two permanently-advisory catalog-completeness checks outside this 14-gate contract entirely: 16 live Stop-tier checks in total . Every pre-check and every one of the 14 end-of-turn gates but two blocks; there is no silent "warning" tier for those see Fire level fire-level . The two documented exceptions are gate.self wired and gate.canon fingerprints advisory below , advisory-only checks that by design never block. Verifier weakening : a check silently neutered content.verifier predicate weakened loose-comparator verifier startswith / endswith / re.match where == is meant content.verifier exit masking exit-code masking || true , ; true , set +e on a test/build/lint content.verifier body hollowed hollowed verifier body return True / pass in a constitution check content.env gated audit audit/verification code gated behind an env var · content.integrity suppression flag integrity-named suppression flag skip = true Fabricated evidence : a claim with no backing artifact content.phantom citation phantom citation Author-Year not in makoto/docs/CITATIONS.md content.unsourced webfetch WebFetch of a URL never seen in any prior tool result this session content.fabricated commit sha fabricated commit SHA/tag presented as proof of a commit content.deferred checkbox theater DEFERRED -style checkbox theater on an open to-do item content.illusory authorship trailer an illusory AI-authorship commit trailer the agent never actually wrote Security-verifier disabling : each a named CWE content.cert verify disabled / content.cert none mode / content.cert reqs none TLS / peer-certificate verification disabled verify=False , CERT NONE content.jwt signature disabled / content.jwt none alg JWT signature verification off / none algorithm whitelisted content.paramiko host key weakened paramiko SSH host-key check weakened to AutoAddPolicy · content.timing unsafe compare timing-unsafe == on a secret/HMAC/digest Self-defense content.self mute guard makoto self-mute disabling or un-wiring makoto via settings.json Scope & contract discipline : illusory progress and out-of-contract action SPEC-5, ported by shape from Assay event.thrash revert a whole-file Write that reverts a file to an earlier byte-identical content after an intervening different Write A→B→A, no net progress event.forbidden location a Write/Edit whose target resolves outside the working directory or into a protected system/credential location gate.contract order a result-producing call issued while a declared Plan's dependency for that step is still undischarged its Stop-time sibling gate guards the remainder at turn end End-of-turn gates : fire on the agent's closing claims, checked against the recorded ledger: gate.completion : "done / created X " but the artifact isn't on disk gate.advance : advancing a phase whose precondition isn't recorded as met gate.green claim : "suite green" against a recorded test failure gate.dropped : a forward promise carrying identifying info "I'll add def X to Y ", "3 tests in Z " left undischarged at turn-end, said-but-not-done, checked against the agent's own touched-keys gate.fabricated action : "I ran X / executed Y " in a turn with no tool call at all presence-of-work, not command-text match gate.named test : " test foo passes" against a recorded FAILED of that exact named test coreference-pinned; the green claim delta gate.stale pass : "all tests pass" against pytest's own lastfailed record still naming a live failing test the runner's ledger, existence-filtered for staleness gate.liveness : fires on the code itself, not a claim: a statement with no live effect inside a closed function dead/illusory work the present-closure model can prove inert . It walks the turn's touched .py ASTs, so it yields a finding per illusory statement rather than one per turn. gate.self wired : advisory only, never blocks see Fire level fire-level . Partial-strip detection of makoto's own .claude/settings.json hook wiring: fires if PreToolUse / PostToolUse / Stop is missing a makoto-dispatching entry while at least one other still has one. It has a documented blind spot: a single edit that strips all three simultaneously disables this check in the same instant it would have fired Claude Code reloads hook config live, not once at session start , so it provides zero coverage against that full-strip case. See the ConfigChange watch configchange-watch-advisory--evidence-gated-blocking section below, which closes exactly this gap when wired. gate.hollow test : fires on the code itself, not a claim: a HOLLOWED test SPIRIT.md §4 , one that survives in name while its content is gutted, so it can never actually fail. Four sub-patterns: no assertion of any kind in the test body; an asserted tautology assert True , or comparing an expression to itself ; a broad, no-op try / except that silently swallows the only call-under-test's failure; and a test-shaped function that can never fire independently, either nested inside another function pytest's collector never discovers it or gated behind a skipif / skipIf condition that is provably always true. gate.canon : blocking level="error" , unlike the advisory exceptions below . Two language-agnostic Stop primitives over the turn's own call stream, reading only closed protocol fields tool name/tool input identity, tool response.interrupted / .error , no test-runner regex, no language token. canon.timeout fires when the turn's LAST tool call ended in a direct error state interrupted, or a self-emitted error code and closed without resurfacing or resolving it; an error resolved by a later successful call stays silent. canon.recur fires when the SAME tool call identical name + byte-identical input was re-issued back-to-back and every call in that consecutive run ended in the same direct error state: a stuck retry loop with nothing changed between attempts; a later success in the same run silences it. Certified via held-out adversarial RED fixtures planted cases that must fire plus a near-vacuous corpus-FP check: the honest corpus almost never carries the triggering precondition interrupted / self error code at all, so a clean corpus replay alone is inconclusive, not a certification see ANCESTRY.md §2 Part B for the corrected framing . gate.canon fingerprints : blocking . 4 of 17 ported session-level "canon" fingerprints SPEC-5 Task 9, from REF-lever-graded-primitives/signalminer/grade planted.py 's 27-fingerprint THE CANON that are robust-core, blocking-capable by construction: 0-FP on both the planted-clean and real-Claude-gold negative sets per the gold-oracle certification nogreen checkdisabled , nosrc destruct , nosrc green timeout , notestedit destruct . gate.canon fingerprints advisory : advisory only, never blocks . The other 13 of the same 17 fingerprints, each either resting on a soft/claim atom claimed pass no run , tool timeout , assertion weakened the gold-oracle finding doesn't certify as robust, or one of that finding's explicitly-named worst-disqualified fingerprints. Recorded to the audit log per SPEC-5's total-retention rule, never emitted as a block decision. gate.contract order : blocking . The Stop-time remainder guard over a declared Plan SPEC-5, ported by shape from Assay's ContractOrder pattern ; fires when the turn ends with the plan's dependency remainder still non-empty. Its PreToolUse sibling guard is a pre-check, not a Stop gate; see makoto/checks/contractOrder.py . gate.stale establisher : advisory only, never blocks , opt-in. A declared Plan node recorded DONE whose named artifact no longer exists on disk the one filesystem read this check family makes, ported by shape from Assay's stale establisher . A product decision to escalate this to blocking is left to the caller; it is not made here. gate.undeclared falsifiable : advisory only, never blocks . A completeness audit over the checks/ catalog itself: an orphan module the loader can't discover, or a declared id with no corresponding live module. A maintenance signal about makoto's own catalog, never a live integrity finding about the agent's turn. Inspect the live catalog with makoto pattern list ; see one pattern in full with makoto pattern show content.phantom citation . gate.canon fingerprints and canon.timeout within gate.canon read the session's own recorded call stream. Once a fingerprint's atoms go true they stay true forever, so without a real discharge path a single sanctioned action e.g. an owner-approved destructive command would otherwise block every remaining Stop for the rest of the session. The only discharge is an operator-attributed release , re-derived from the host-written transcript at check time and never trusted from ledger content, so no tool call or file write can forge it. Say, as a real message in the conversation never inside a tool call or file write : makoto release.operator