DEV Community

Cover image for Claude Code 2.1.294 Fixed Guards That Silently Failed: Hooks That Allowed, Rules That Never Loaded
Piekwerk
Piekwerk

Posted on

Claude Code 2.1.294 Fixed Guards That Silently Failed: Hooks That Allowed, Rules That Never Loaded

Two quiet fixes landed in Claude Code this week, and both are about the same thing: guardrail config that looks active and is not. Version 2.1.294, published to npm at 03:42 UTC on October 8, fixed instruction-style hooks that "allowed what they should block", in the project's own words. The same release fixed path-scoped rules and nested CLAUDE.md files that never loaded at all when Claude viewed a file through a Bash command instead of the Read tool. If you maintain rules files or hooks for a living codebase, neither bug produced an error message. Your tests could pass. Your audit could pass. The guard simply was not there.

I write rules files and hook configs for our agent kits, so when a fix like this ships I do not ask "am I affected", I ask "how would I have known". The honest answer for both bugs is: only by testing the behavior, never by reading the config. That is worth walking through, because the audit is short and the failure class is bigger than one patch.

What the changelog actually says

Verbatim from the official changelog, version 2.1.294:

  • "Fixed prompt and agent hooks written as instructions (such as "Block commands that..") allowing what they should block"
  • "Fixed path-scoped rules and nested CLAUDE.md files not loading when Claude views a file with a single-file cat, head, tail, sed -n or grep command in the Bash tool instead of the Read tool"

And one line up, 2.1.293 (npm, October 7, 17:18 UTC):

  • "Improved how prompt hooks on Stop and SubagentStop written as instructions (such as "Carry on if the build is broken") are judged, so Claude is less likely to stop early"

Read the first line again if you write hooks. A hook existed, was registered, ran on every matching event, and could allow exactly the command it was written to block. Not fail loudly. Fail open.

The failure mode: guards written as prose

Claude Code hooks come in two flavors. A decision hook is a script that inspects the event and returns a verdict, and the verdict is enforced mechanically. An instruction hook, the prompt and agent kinds, is prose that gets injected and judged by the model. "Block commands that touch the deploy bucket." That is a policy written as a sentence, and until 2.1.294 the judgment of that sentence could go the wrong way on exactly the input you wrote it for.

The enforced form looks like this:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "./guards/block-pattern.sh" }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Where block-pattern.sh exits non-zero with a JSON body, {"decision": "block", "reason": ".."}, on a match. That path is code, not judgment. The fix in 2.1.294 is real and welcome, but it is a patch to a judge, and judges get patched again. My rule after this week: anything that must never happen belongs in a script with an exit code, and prose hooks are for preferences, not prohibitions. This is the same conclusion I reached when a 7-line Claude Code mod overrode a deny rule, and this changelog line is more evidence for it.

The other fix: rules that never loaded

The second bug is sneakier because it depends on how Claude reads a file. Your path-scoped rule for src/payments/** loads when Claude uses the Read tool. When Claude instead looks at the file with a single cat, head, tail, sed -n or grep in the Bash tool, the rule did not load. At all. So the same file, the same session, different tool path, different rule set.

Think about what that does to verification. You test your rule by asking Claude about a file, it loads, the behavior is correct. Later, Claude reaches the same file through grep in a pipeline, and your rule about payment code, migration safety, or secrets handling is simply absent. Nobody sees an error, because not loading a file produces no error. I catalogued seven ways agents ignore rules files, each with a fix, in an earlier piece, and this is a new entry for the list: the rule is fine, the loading path ate it.

The 10-minute audit

Two probes, run after upgrading to 2.1.294, catch both classes in your own setup.

First, inventory your instruction hooks and look for prohibition-shaped prose:

grep -rn -iE 'block|never|forbid|do not|dont' .claude/settings*.json .claude/hooks/ 2>/dev/null
Enter fullscreen mode Exit fullscreen mode

Every hit in a prompt or agent hook is a candidate for conversion to a decision script. The script does not need to be clever, a case statement over the command string catches most of what prose was trying to catch, and its verdict does not depend on how a model reads a sentence that day.

Second, test rule loading through both read paths. Pick one rule you believe is active, one that produces a visible behavior, and trigger it twice: once in a fresh session where you explicitly ask Claude to read the file, once where the file comes up through a Bash grep. If the behavior appears in one and not the other, you have found a load-path gap, and now you know which of your rules are conditional on tool choice. Re-run this when the client version changes, because loading behavior is exactly the kind of thing that gets fixed and re-broken quietly, as this week shows.

Why "it worked when I set it up" proves nothing

Both bugs share a property that should change how you review agent config: the setup-time check passed. The hook ran, the rule existed, a reasonable person would have called the config correct. The failure was in the runtime judgment and the runtime load path, invisible from the file itself. That is why I stopped treating config review as a reading exercise and started treating it as a test exercise, with probes that exercise the behavior, not the text. It is the same discipline I apply when scanning agent configs for silent failures: the file on disk is the claim, the probe is the evidence. The version-pinned kits we ship at AgentConfig Studio exist for the adjacent reason, so that when the client changes underneath your rules you can tell what changed and when, instead of noticing a guard missing after the fact.

The rest of the release worth thirty seconds

2.1.293 also fixed a memory leak where an HTTP MCP connection kept every request it had sent until it closed, which matters if you run long sessions against remote servers, and added Claude Haiku 5.5 (claude-haiku-5-5) with a 1M context window at $0.10/$0.50 per Mtok, the new default Haiku on the Anthropic API. For mods authors, isDeferred in $.tool.register now controls whether a tool's schema is listed in the prompt from the start or behind tool search, which is a context-budget lever dressed as a registration flag. And if you missed it, 2.1.292 flipped the stdio MCP default to the 2026-07-28 protocol, which I probed server by server here.

The short version

Upgrade, then re-earn your trust in two places: convert prohibition hooks from prose to scripts with exit codes, and probe your path-scoped rules through both read paths. Ten minutes, and you stop depending on patches to a judge you cannot see.

Top comments (0)