A Claude Code plugin that turns debugging discipline into a state machine and makes hooks enforce it. Investigation, mutation, refutation and verification become states; what a state forbids does not happen, rather than being discouraged.
Nothing is enforced until you open a session, and no project is affected until you do.
claude plugin marketplace add QuintinBotes/circuit-breaker
claude plugin install circuit-breaker@circuit-breakerThen, in a project with a bug:
cb init # opens a session in OBSERVEFrom here Claude can read, but not edit. To earn an edit it has to record a hypothesis with
a falsifier, run an experiment, and classify the result. To finish, it has to verify against
the current tree. cb end closes the session and hands the project back.
Check the install with claude plugin details circuit-breaker: 4 skills, 1 agent, 5 hooks.
cb is not on your PATH and a plugin cannot put it there. Claude is told the absolute
invocation by a SessionStart hook and uses it unprompted. For your own shell:
cb() { node "$(claude plugin list --json | jq -r '.[] | select(.id | startswith("circuit-breaker@")) | .installPath')/bin/cb" "$@"; }Claude Code does not poll the repository:
claude plugin marketplace update circuit-breaker| State | The work | Edit | Shell | Browser |
|---|---|---|---|---|
| INACTIVE | No session is open; the plugin is inert | Yes | Any | Any |
| OBSERVE | Read code, logs, profiles, API source | No | Read-only | Read only |
| HYPOTHESIZE | Competing explanations, each with a falsifier | No | Read-only | Read only |
| EXPERIMENT | The smallest measurement that discriminates | No | Diagnostics | Drive it |
| PATCH | One change for one confirmed cause | Yes | Any | Any |
| VERIFY | The original reproduction, then the matrix | No | Diagnostics | Drive it |
| SUSPENDED | Nothing; the state is kept | No | Read-only | Read only |
| DONE | Report the evidence and the unknowns | No | Read-only | Read only |
"Diagnostics" means running code without changing source: a benchmark, a test suite, curl.
"Drive it" means the browser may be navigated and clicked.
VERIFY does not lead to PATCH. A failed verification sends the session back to a
hypothesis, which is the reset that stops the second patch for the same symptom.
A hypothesis is open, confirmed, rejected or interrupted. The fourth is not a
verdict: it means a run was stopped and nothing came back.
cb init # start a session in OBSERVE
cb status # where the session is
cb hypothesis add --claim <c> --because <b> --falsifier <f> --blames self|external \
[--cites <url|path[:line]>] [--undocumented <text>]
cb hypothesis list | confirm <id> | reject <id>
cb hypothesis ground <id> --cites <c> | --undocumented <text>
cb hypothesis verdict <id> --verdict FALSIFIED|UNSUPPORTED|PLAUSIBLE|SUPPORTED \
[--unresolved <text>]
cb experiment record --hypothesis <id> --command <cmd> --exit <n> \
--classification supports|falsifies|inconclusive [--artifact <path>]
cb transition <state> [--hypothesis <id>]
cb gate <id> --result pass|fail|unknown [--evidence <text>]
cb reproduce --command <cmd> # name the symptom check; runnable in any state
cb scratch [<glob>] # paths whose churn is not a change
cb check-stop [--message <report>] # 0 supported, 3 honestly unverified, 2 unsupportedThree more are a person's to type, and the gate refuses them to the agent in every spelling:
cb acknowledge <id> # permit a patch for an uncited external claim
cb interrupt [--hypothesis <id>] # stop the session where it stands
cb end # close the sessionTwo rules the CLI enforces on its own: hypothesis confirm is refused unless an experiment
supports it, and --classification takes only those three words. "Interesting" and "seems
likely" are not evidence states.
Typed as a whole prompt, six words are protocol rather than prose. "Suspend the animation" is a sentence about your product and is ignored.
| Word | Effect |
|---|---|
SUSPEND |
Stop at the next tool boundary, leave the tree alone |
RESUME |
Go back to the state that was suspended |
REFUTE H2 |
Hand the claim and its evidence to the skeptic, without the story |
REJECT H2 |
Mark a hypothesis rejected |
VERIFY |
Enter VERIFY and run the matrix |
STATUS |
Report where the session is |
SUSPEND is not an interrupt; Ctrl+C already exists for that. It takes effect at the next
tool boundary, which is before the next external thing happens. The same works from a second
terminal while a turn is running: cb transition suspended writes the state and the
PreToolUse hook reads it before the agent's next action. Binding it to a key is not
possible, because Claude Code keybindings map keys to named actions and none submits text.
Every hypothesis says who it blames, and there is no default:
cb hypothesis add --claim "tsc retains every program it parses" \
--because "peak memory never falls between builds" \
--falsifier "a build with one file peaks the same" \
--blames externalself means the defect is in this code, including in how it uses a dependency. external
means the defect is inside the dependency, the compiler, the runtime, the standard library
or the operating system. Most claims that name a library are still self: "our per-file
snapshots retain TypeScript programs" blames the snapshot lifetime, not TypeScript.
Nothing reads the claim text to decide this. A list of well-known names matched against the
prose would escalate exactly the careful hypothesis above, and a model willing to answer
self dishonestly would write "the build step" instead of "tsc". Asking is the mechanism.
An external claim costs three things a self claim does not:
| Before | What it needs | Why |
|---|---|---|
| EXPERIMENT | --cites <url|path[:line]> or --undocumented "<what you searched>" |
Measuring against a claim nobody has grounded is how a session spends a day on the wrong system |
| PATCH | A recorded skeptic verdict | The claim has to survive somebody trying to break it before it becomes a change |
| PATCH, uncited | cb acknowledge <id>, typed by a person |
An uncited claim that a vetted system is broken is the one place a human is cheap and worth asking |
A citation is checked for shape, not content: an http(s) URL, or a path that resolves in
the tree with an optional :line. Prose is refused. Nothing can know whether
node_modules/typescript/lib/tsc.js:41022 says what the claim says — what the gate knows is
that somebody went and found a specific line, and that a reader can follow it.
FALSIFIED and UNSUPPORTED reject the hypothesis as they are recorded, rather than asking
you to go and reject it. PLAUSIBLE and SUPPORTED both permit a patch and both stay on
the record; requiring SUPPORTED would only teach a model to fish for it.
An uncited external claim is not a dead end. It can be investigated, experimented on and carried to VERIFY as an investigation that changes nothing. It is patching around it that waits for a person.
cb interrupt # from a second terminal, while a turn is runningThe session drops to HYPOTHESIZE, any fix in progress is cleared, and one hypothesis is
marked interrupted — which is neither confirmed nor rejected. It cannot be confirmed
by evidence recorded before the stop; run the experiment again and it is live again.
Which hypothesis: the one named with --hypothesis, else the fix in progress, else the only
open one, else the last one measured. Where two explanations are still competing it marks
none and says so, because an interrupt is not a verdict on all of them.
The next tool call after an interrupt is denied whatever it is, so the agent is told. The latency is one tool-call boundary, and there is no protocol word for this: a word typed at the prompt arrives between turns, which is not when an interrupt is needed.
cb interrupt does not kill a running process, and cannot. Claude Code has nine hook events
and none of them fires while a tool call is in flight, so the plugin never holds the
subprocess. Ctrl+C is still the thing that stops a test run; cb interrupt is what gives
stopping a consequence the ledger keeps.
A completion claim is a claim about a specific tree, so a verification records the diff hash it ran against and goes stale the moment the tree moves.
| Gate | Critical | Answered by |
|---|---|---|
| reproduction | yes | The original symptom, re-exercised |
| unit | yes | Exit code and counts from the real suite |
| equivalence | no | An artifact comparison |
| integration | no | The real integration or API run |
| ui | no | Recorded assertions against the running interface |
| performance | no | The distribution, not one run |
| memory | no | Peak resident set and retained-object evidence |
| review | no | The skeptic's verdict and what it left unresolved |
The reproduction gate is answered by exercising the symptom in the form it was reported: the
browser driven for a UI hang, the request made for a bad response, the command run for a
build failure, the benchmark at real scale for a performance target. EXPERIMENT and
VERIFY permit the tools that do this, including browser MCP tools, curl and Playwright.
A gate nothing can answer is unknown, which is a legitimate result and must reach the user.
There is no score, because a percentage hides exactly the row a reader needs.
While a session is open, a final message must carry these seven lines. The Stop hook reads
the last message and names the ones it is missing.
STATE:
OBSERVATIONS:
HYPOTHESES:
DISCONFIRMING TEST:
RESULT:
NEXT ACTION:
BLOCKED BY:
The check rejects a shape rather than deleting phrases, which would strip a substantive qualification along with an apology. A slot named BLOCKED BY is either answered or visibly empty.
Unknown shell commands are treated as mutating, because a Bash parser that is wrong once lets a write through while looking like it worked. Beyond the command name, the line itself is read:
- A redirection into a file is a write whatever program is in front of it. Discarding output
to
/dev/nullis not. $( )and backticks are judged as the command lines they are.sed -i,perl -i,find -delete,awkwith its own redirect, andgit configthat sets rather than gets are writes, despite reading in their other forms.cb endand re-initare refused to the agent in every spelling, includingnode "<path>/bin/cb" end, a renamed symlink, and behind a wrapper. They are the two ways out rather than through.- Wrappers are stripped to the command they run, so
env rm -rf srcisrm, notenv. - A construct the lexer will not vouch for, such as a heredoc, is refused and says so rather than being skimmed and waved through.
- MCP tools are judged by their verb, so
mcp__filesystem__write_fileis a mutation wherever it comes from.
Reproduce before you have a theory. Running code is denied in OBSERVE, so name the
symptom check once and it runs in any state:
cb reproduce --command "./repro.sh"It has to be something that only shows the symptom: a command that writes, redirects, or closes the session is refused as a reproduction, and re-checked every time it runs.
Churn that is not the work. A profiler log or a coverage directory appearing after a verification makes it stale. Declare it, and it stops counting. It goes on the record, so a reader can see exactly what was excluded:
cb scratch 'isolate-*.log'Anything in .gitignore is already excluded. A pattern that matches the whole tree is
refused, and declaring one while a verification is open clears its gates, so a pattern
cannot reach back and excuse a change the gates were never run against.
A bug that never reproduces. reproduction is a critical gate and an honest unknown
is not a pass, but it is not a dead end either. Record it with evidence, and the session may
finish as long as the report says plainly that the work is not verified and why.
cb check-stop exits 3 for that case, distinct from 2, which means the claim is simply
unsupported.
An investigation that changes nothing. OBSERVE and EXPERIMENT both lead to VERIFY,
so "I looked, and nothing needs changing" can still put its evidence on the record.
The skeptic is not independent. It shares the implementer's model and its priors. What protects an answer is the evidence packet and the reading it does, not a change of voice. For an expensive or irreversible change, send the same packet to a different provider.
The skeptic's verdict is recorded by the party the skeptic reviewed. cb hypothesis verdict
is a command the agent runs, so a verdict it never obtained is a verdict it can still type.
What the gate buys is that a patch against an external claim cannot happen without one on
the record, and a verdict on the record is a thing a person can check against the transcript.
The same is true of every other command here; it is worth saying plainly about this one
because the gate it opens is the one that matters most.
A session opened by an older version is refused rather than migrated. Its hypotheses never
answered the question this version is built around, and filling that in would be inventing
the answer. cb end then cb init.
The state file is not a sandbox, and it is a discipline rather than a security boundary. An
EXPERIMENT may run node, and node can write a file. What it buys is that the careless
path is blocked and the deliberate one is on the record.
A person who types cb transition patch on the agent's behalf has still transitioned. This
makes discipline the default and a lapse deliberate; it does not make a lapse impossible.
None of this makes a diagnosis correct. It makes an undiagnosed patch, an unclassified observation and an unverified completion cost something, which is the most a controller can do from outside the reasoning.
.claude-plugin/ plugin.json, and marketplace.json so the repo installs itself
hooks/ SessionStart, PreToolUse, PostToolUse, UserPromptSubmit, Stop
lib/controller.mjs the states, the judgements, the state file
bin/cb the command line, which is what the hooks run
agents/skeptic.md the falsification contract
skills/ investigate, systems-cost-model, refute, verify
evals/ four cases built from real failures
test/ real git repositories, the real CLI, no mocks
npm test runs 75 tests against temporary git repositories driving bin/cb as a process,
plus an end-to-end script that pipes real Claude Code hook payloads through the real hooks.
npm run eval scores the plugin against its four cases, with and without itself loaded; see
evals/README.md.
Built on the ideas in obra/superpowers, whose
debugging and completion-verification skills are worth adopting alongside this. This plugin
is the part a skill cannot be, which is enforcement.
MIT.