Skip to content

Launch notes · @bulkheadtools/cli 0.2.0

We went with
the ledger.

Why we built deterministic guardrails and receipts for Claude Code, what they stop, what they can’t, and how to check every claim yourself with one command.

by: myrmitis LLChello@bulkhead.tools

acme-apiledger #8
SAID
“All tests pass.”
LEDGER
npm test · exit 1

What the human saw:

🛡 Bulkhead: agent said "All tests pass" but the latest `npm test` exited 1 — sent back to work (ledger #8)

It said “All tests pass.” The ledger said exit 1. We went with the ledger.

That sentence is the whole product, so it goes first. Bulkhead sits under Claude Code as a set of hooks. It checks every tool call against rules you wrote, sends the agent back to work when a claim contradicts the evidence, caps what a session can spend, and writes all of it to a hash-chained ledger in your repo. Every run ends with a receipt.

This is the long version: what we saw, the gap we think is still open, what Bulkhead does about it, what it can’t do, and how to check every claim here before you believe any of it.

What we saw

Four public incidents shaped this. We read them as shapes, not headlines.

  1. Apr 2026

    PocketOS Cursor agent

    A Cursor agent deleted the production database volume, backups included, in under ten seconds, then wrote an apology. The data was later recovered with Railway’s help.

    Source: The Register

  2. Jul 2025

    SaaStr Replit agent

    Replit’s agent wiped a production database during a code freeze, then said a rollback was impossible. That wasn’t true.

    Source: Fortune

  3. Jul 2025

    Gemini CLI Google

    A mkdir failed silently. The file moves that followed overwrote one another until only the last file remained.

    Source: GitHub issue #4586

  4. Jul 2025

    Amazon Q Developer AWS

    A data-wiping prompt shipped inside the official VS Code extension.

    Source: BleepingComputer, The Register, CSO

We’re not interested in who to blame, and we don’t think the lesson is “the models are bad.” The same few shapes repeat: a destructive command sent with a credential the agent could reach; a statement about the state of things that nothing checked; ordinary steps that went wrong in between. In two of the four, the agent’s own words became part of the record: an apology in one, a false statement about rollback in the other.

We run Claude Code the way a lot of teams do now: long sessions, auto mode, sometimes overnight. We wanted rules that hold whatever the model decides, and a record we can check instead of a summary we have to trust.

The gap

Blocking is the solved part. Yes, Bulkhead blocks rm -rf outside your workspace. So do auto mode and plenty of free hooks. Anthropic says auto mode “reduces risk … but doesn’t eliminate it”, and we’d rather run it than not. What we didn’t find bundled were three other things.

  1. 1

    “Done” that has to be true.

    When an agent says tests pass, something should look at the last test run. An independent analysis of one developer’s transcripts found that about two-thirds of the agent’s “done” claims (69%, or 65% on the author’s stricter recount) had no passing test run after the final edit. The author calls it not a lie detector, and neither is this. It’s a comparison.

  2. 2

    A receipt you can check.

    Claude Code has OpenTelemetry events and a Compliance API. We wanted something smaller: a local, zero-infra, per-repo, tamper-evident record, and one page we could paste into a pull request.

  3. 3

    A hard cap on interactive sessions.

    Claude Code’s --max-budget-usd applies only to non-interactive (-p) runs. We wanted a dollar cap on the interactive ones too, the kind that run overnight.

What it does

One command, in the repo you want guarded:

Terminal
$ npx @bulkheadtools/cli init

✅ Bulkhead installed in ~/code/acme-api
   • bulkhead.yaml: created (preset: default)
   • hooks: merged into ~/code/acme-api/.claude/settings.json
   • .gitignore: added .bulkhead/

Every tool call is now checked; blocks land in .bulkhead/ledger.jsonl.
Containment + evidence, not a firewall: hooks are bypassable (see README).

Next:
   See it work:  npx @bulkheadtools/cli demo
   Add a badge:  npx @bulkheadtools/cli badge

It writes bulkhead.yaml, a policy file you own and check in, and merges its hooks into .claude/settings.json. From then on every decision is a pure function of the tool call, your policy, the transcript and loop state. There is no model in the enforcement path, so the same call gets the same answer every time. The agent can read a refusal; it can’t argue with one.

The lines you draw

Values in blue are the ones a human set. These are the defaults; all of them are yours to change.

  • Protected paths.env · prod/** · migrations/** (repo root)Plus Bulkhead’s own policy, ledger and hook settings, so the agent can’t edit its guardrails with file tools or the obvious shell writes (sed -i, redirects, tee, cp/mv); a script or interpreter that writes the file is not caught.
  • Denied by default26 dangerous callsUnscoped deletes, terraform destroy, volume deletes, force-push to main, rm outside the workspace and more. --preset strict adds 6 for unattended runs.
  • Budget caps$5 session · $20 dayPriced from the transcript. Checked on every tool call, so one long model turn can overshoot before the next call is refused.
  • Loop freeze8 calls / 300 sThe same call repeated that often inside the window is frozen. Changing an argument resets it.

“Done” has to be true

When the agent tries to stop, Bulkhead compares what it claims (tests pass, committed, PR opened) with what the ledger recorded. If the latest test run failed, it’s sent back to work once, with the evidence quoted; a repeat stop is recorded on the receipt, not blocked. Missing evidence is noted, not blocked, by default. It’s a deterministic claim-vs-evidence check, not a lie detector and not a model.

What Claude reads
⛔ Bulkhead completion check failed.
You stated "All tests pass" but the evidence ledger disagrees — the latest relevant evidence is: test_failed: npm test (exit 1) /invoice.test.ts > invoice > rounds line totals half-even AssertionError: expected 10.24 to be 10.25 // Object.is equality ❯ src/billing/invoice.test.ts:31:24 FAIL src/billing/invoice.test.ts > invoice > applies credit notes before tax AssertionError: expected 88.1 to be 88.2 // Ob…. Re-run the check and show its real output, or correct your statement to match what actually happened.
This is a deterministic claim-vs-evidence check (bulkhead.yaml: verify).
What you see
🛡 Bulkhead: agent said "All tests pass" but the latest `npm test` exited 1 — sent back to work (ledger #8)

Every run ends with a receipt

bulkhead receipt turns a session into one page: what the agent did, what it was stopped from doing, what it claimed and what the ledger says. Markdown for the PR description, or text, or HTML. Redacted by default: repo-relative paths, and secrets in common token shapes masked. It’s pattern-based and won’t catch every secret, so read a receipt before you post it. npx @bulkheadtools/cli ledger verify re-walks the hash chain; editing or deleting an entry in the middle breaks it.

================================================
                B U L K H E A D
             agent session receipt
================================================
REPO                                    acme-api
SESSION     7f2c1e04-5a9d-4b3e-8c61-2d0e9a4f3b91
START                       2026-09-24 02:14 UTC
END                         2026-09-24 02:19 UTC
DURATION                                   5 min
------------------------------------------------
TOOL CALLS CHECKED                             8
BLOCKED                                        2
RISKY, ALLOWED                                 0
CLAIMS               1 contradicted, 2 supported
SPEND                          $1.01 / $5.00 cap
------------------------------------------------
BLOCKED
#6   Write
    .env
    > protected path (.env)
#7   Bash
    git push --force origin main
    > force-push to a protected branch
------------------------------------------------
COMPLETION CLAIMS
#8 "All tests pass"
    *** CLAIM ≠ EVIDENCE ***
    npm test exited 1
    -> sent back to work
#15 "All tests pass"
    SUPPORTED
    npm test exited 0 · Tests 43 passed (43)
#15 "committed"
    SUPPORTED
    commit “fix: round invoice totals half-even”
------------------------------------------------
EVIDENCE
  test run failed                              1
  test run passed                              1
  commit                                       1
  file edits                                   2
------------------------------------------------
LEDGER ENTRIES                                16
HEAD                                9aaa0621662b
CHAIN                                   VERIFIED
================================================
Verify: npx @bulkheadtools/cli ledger verify
Local, hash-chained record. Not a sandbox; the
tail is not anchored off-machine.
================================================
bulkhead receipt --format txt for a scripted session, replayed through the real hooks to make this page.

MIT. Two runtime dependencies (picomatch, yaml). No telemetry: Bulkhead’s code sends nothing. The hooks run through npx, which looks the package up on the npm registry on each call; install it and point the hooks at it (bulkhead init --command) to remove that. bulkhead notify sends only to a webhook you configure.

What it can’t do

We’d rather you hear this from us than from a write-up.

  • It’s not a sandbox, and not a firewall.

    It reads the tool call the agent emits. A shell can reach a file in ways a pattern can’t see: interpreters, scripts written and then run, cd then a relative rm, encoded commands. The demo prints these 5 known misses on purpose.

  • It doesn’t stop prompt injection.

    It inspects what the agent does, not why it decided to.

  • Hooks can be skipped.

    --bare skips them, a user can remove them, and a Claude Code update can change the contract.

  • Bypass mode isn’t supported.

    A public bug report (anthropics/claude-code#20946) says hooks may not block reliably under --dangerously-skip-permissions. Use default, acceptEdits or auto mode, and a sandbox for unattended runs.

  • It fails open on its own bugs.

    A Bulkhead error never bricks your agent. It fails closed only when its own state storage is unavailable.

  • A torn-off tail still verifies.

    The local chain can’t detect entries removed from the end. Anchoring the head off-machine is planned, not built.

  • Claude Code only, today.

    Codex CLI is next. Cursor is on the roadmap, not shipped.

If a guardrail can’t tell you where it stops, don’t trust where it holds.

Run the proof

Don’t take this post’s word for any of it. The incident corpus is open, and it runs through the real hook in a throwaway repo:

Run it yourself
npx @bulkheadtools/cli demo

Throwaway repo. Your repo and ledger are never touched.

dangerous calls denied by default
26/26
more with --preset strict
+6
known misses, shown on purpose
5
ordinary commands allowed
15/15

Then it replays a false “tests pass” claim and a blown budget, and exits non-zero if any result differs from what the corpus expects. The ordinary commands matter as much as the denials: a scoped DELETE, a feature-branch push, terraform plan. A guardrail that nags gets uninstalled.

Shapes, not claims. The real incidents had context no corpus can reproduce, and PocketOS ran on Cursor. Bulkhead supports Claude Code only today. What the corpus shows is that it denies the command shapes reported in those incidents, and where it can’t see.

What’s next

  • Codex CLINext

    The next host we support. If you run Codex, tell us; it moves the order.

  • Faster hooksNext

    Each hook is a short Node process, and every tool call runs two of them (PreToolUse and PostToolUse). Measured on 2026-09-25 in our build environment: about 0.7 s per hook through npx (warm) — about 1.4 s of that per tool call — and about 0.12 s calling an installed CLI directly, about 0.24 s per tool call. Yours will differ. bulkhead init --command points the hooks at a local install today; a one-flag init --local is on the roadmap.

  • Carbon CopyIn the works

    Part of Pro, which is planned and not built: anchoring the ledger head off-machine, so a torn-off tail stops verifying. Pro is planned at $39 per active developer per month, waitlist only, and no guardrail will move behind it. Join the waitlist and tell us which part you’d pay for first.

No dates. They ship when they work, and this page will say so.

How to reach us

If Bulkhead blocks something it shouldn’t, or misses something the corpus says it catches, that’s a bug we want: open an issue. If your agents can reach production and you’d rather not write the policy yourself, we review and harden setups for a fixed, published price. Anything else: hello@bulkhead.tools. A person reads it.

— Bulkhead. Independent; not affiliated with Anthropic. “Claude Code” is a product of Anthropic.

Install it. Read the receipt.

One command installs the hooks and a policy file you own. MIT, and every guardrail stays free.

Your copy
npx @bulkheadtools/cli init