# Claude Code 2.1.299 to 2.1.301 Changed How Your Hooks Behave: 7 Things to Recheck

> Source: <https://dev.to/gabrielanhaia/claude-code-21299-to-21301-changed-how-your-hooks-behave-7-things-to-recheck-373d>
> Published: 2026-10-16 06:02:02+00:00

Since [Claude Code](https://code.claude.com/docs/en/overview) 2.1.299, a `UserPromptSubmit` hook runs before your skill expands, so a hook that blocks the prompt now also stops the skill's shell lines. Nobody touched your hooks file to make that happen.

Three releases landed on October 14 and 15, 2026: 2.1.299, 2.1.300 and 2.1.301 ([changelog](https://code.claude.com/docs/en/changelog)). Seven lines in their "Changed" sections alter how hooks you already have behave. They move when a prompt hook runs, what happens to input a hook rewrites, which MCP tools an agent's `tools:` list picks up, and what a plugin hook does when it can't answer. None of them waits for an edit on your side. They apply as soon as you're on the new version.

Each item below quotes the changelog line, says what it can break, and gives one command to see whether your setup is affected. Two hook changes from the same week are left out on purpose: `onFailure: "block"` (2.1.295) and the narrower MCP allow rules (2.1.299). Each needs more room than a list item.

```
claude --version
claude update
```

Each of the seven changes arrived in 2.1.299 or later. If `claude --version` prints an older number, none of the seven applies to you yet, and `claude update` is the moment they start to. One of the three fixes near the end goes back further, to 2.1.294.

If you run Claude Code through [Hermes IDE](https://hermes-ide.com), which I build, the agent picker in the ⌘N sheet shows the installed version next to each agent you have installed.

*Hermes 2.3.4's agent picker: Claude Code (installed, 2.1.299) selected, Codex installed (0.145.0), and the other supported agents marked Not installed.*

`UserPromptSubmit` now runs before the skill expands
From 2.1.299: "Changed `UserPromptSubmit` hooks to run before a skill or custom slash command expands, so a hook that blocks the prompt now also stops its embedded shell commands."

A skill's body can carry shell lines, written as `!` followed by a command in backticks. The getting-started example in the [skills docs](https://code.claude.com/docs/en/skills) runs `git diff HEAD` that way. The changelog's "now also stops" tells you the old order: a blocking hook stopped the prompt, but not those lines.

What can break: your hook now sees the prompt before the skill's text is in it. A hook that searched the prompt for words that only appear inside a skill's body has nothing left to find. For the expansion itself there is a separate event, `UserPromptExpansion`, which matches on the command's name ([hooks reference](https://code.claude.com/docs/en/hooks)).

To see what can break, print your `UserPromptSubmit` hooks and look for patterns that only match a skill's expanded text:

```
grep -n -A8 UserPromptSubmit \
  .claude/settings.json ~/.claude/settings.json
```

This one lists the skills and commands whose shell lines a blocking hook can now stop:

```
grep -rlF '!`' .claude/skills .claude/commands \
  ~/.claude/skills ~/.claude/commands
```

From 2.1.299: "Changed a skill's own front-matter `UserPromptSubmit` hook to first run on the prompt after the one that invokes the skill."

A skill can declare hooks in its frontmatter. Claude Code registers them when the skill is invoked and keeps running them for the rest of the session ([hooks reference](https://code.claude.com/docs/en/hooks)). If your skill's `UserPromptSubmit` hook checked the prompt that called it, say `/deploy prod`, it no longer sees that prompt. It starts with the next one.

If the check has to happen on the invocation, move it to `UserPromptExpansion` in your settings. The hooks reference gives this exact case: a hook matching `deploy` that blocks `/deploy` unless an approval file is present.

```
{
  "hooks": {
    "UserPromptExpansion": [
      {
        "matcher": "deploy",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/deploy.sh"
          }
        ]
      }
    ]
  }
}
```

To find the skills that declare this hook:

```
grep -rln UserPromptSubmit .claude/skills ~/.claude/skills
```

`updatedInput` is validated, and unknown keys are dropped
From 2.1.300: "Changed hooks: `updatedInput` from PreToolUse and PermissionRequest hooks is now validated like Claude's own input: unknown keys are dropped, and input the tool rejects denies the call."

The [hooks reference](https://code.claude.com/docs/en/hooks) already said `updatedInput` "Replaces the entire input object, so include unchanged fields alongside modified ones." Now that rule bites. A hook that returns only the field it changed and leaves out a required one, or spells a field wrong, hands the tool input it rejects, and the call is denied. A key you added for your own bookkeeping is dropped before the tool sees it.

The safe shape starts from the input Claude sent and changes one field. In a command hook, with [jq](https://jqlang.org):

```
jq '{hookSpecificOutput: {
  hookEventName: "PreToolUse",
  permissionDecision: "allow",
  updatedInput: (.tool_input + {command: "npm run lint"})
}}'
```

The check feeds your hook a sample event and prints what it returns as `updatedInput`, wherever it sits in the output. Swap in your own script path:

```
echo '{"hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {"command": "ls"}}' \
  | .claude/hooks/rewrite.sh \
  | jq '.. | .updatedInput? // empty'
```

Every key in the object it prints should be one the tool accepts, and the object should be the whole input, not only the field you changed.

`setMode` from a `PermissionRequest` hook is ignored in Cowork cloud
From 2.1.300: "Changed `PermissionRequest` hooks in Cowork cloud sessions: a `setMode` update in a hook's allow is now ignored; the rest of the approval still applies."

A `PermissionRequest` hook that allows a call can also send `updatedPermissions`, and one entry type, `setMode`, changes the permission mode, to `acceptEdits` for example ([hooks reference](https://code.claude.com/docs/en/hooks)). Cowork can run tasks [in the cloud, on Anthropic's servers](https://support.claude.com/en/articles/15811196-what-to-expect-with-claude-cowork-in-the-cloud). In those sessions the approval still holds, but the mode no longer moves. A hook that approved the first edit and switched the session to accept edits will keep seeing permission requests.

A related line in 2.1.299 goes further for auto mode in Cowork cloud sessions: "a `PermissionRequest` hook can no longer approve shell commands or subagents; those prompts wait or are denied." Neither line mentions local terminal sessions.

The check finds hooks that send a mode change:

```
grep -rn setMode .claude ~/.claude/settings.json ~/.claude/hooks
```

*Seven changes in two days, none of them in your config file.*

`tools:` lists take only the server they name
From 2.1.299: "Changed agent `tools:` lists to take only the named MCP server's tools; list a server whose name contains `__` as `mcp__<whole name>__*`."

The [subagents docs](https://code.claude.com/docs/en/sub-agents) say `mcp__<server>` or `mcp__<server>__*` in `tools:` grants every tool from the named server. The allow-rule line in the same release spells out the old leak: `mcp__a` used to also reach a server named `a__b` or `a_`. Agent `tools:` lists got the same narrowing. An agent that quietly picked up a second server's tools through a shared prefix loses them. If no entry in its list resolves to a tool anymore, the docs say the subagent usually fails to launch.

Compare the MCP entries in your agent files with the real server names:

```
grep -rn 'mcp__' .claude/agents ~/.claude/agents
claude mcp list
```

From 2.1.299: "Changed actions a plugin hook can block to be refused, not let through, when the hook cannot be handed them or cannot hand back its answer; a tool that already ran has its result withheld."

This one flips a failure from open to closed. When a plugin hook couldn't receive an action or couldn't return its answer, the action used to go through. Now the action is refused. If the tool already ran, it stays run, and Claude never sees its result.

The [hooks reference](https://code.claude.com/docs/en/hooks) says `claude --debug` writes a log to `~/.claude/debug/<session-id>.txt`. From 2.1.296, command hooks on tool events, prompts, `SessionStart`, `Stop` and several other events leave a `Hooks:` line with the command, the plugin it came from if any, and the outcome, ending in a duration or in `timed out after <N>ms`, `cancelled` or `failed to start`. Run a session with `--debug` and start here:

```
grep -hE 'timed out|failed to start' ~/.claude/debug/*.txt
```

Only lines that name a plugin count, and a match only tells you where to look. A hook from your own settings has nothing to do with this change, and `/hooks` labels each hook with where it comes from, so you can tell the two apart. Plugins built as mods don't write `Hooks:` lines, so for those, start with the Errors tab in `/plugin`.

`.catch` no longer hears about re-entry
From 2.1.301: "Changed a mod hook's `.catch` handler to run only when the hook throws or times out; events raised beneath the hook's own calls now skip that hook without asking its `.catch`."

The [mods reference](https://code.claude.com/docs/en/plugins/mods/reference) lists three values for `next.error.kind` in a `.catch` handler: `throw`, `timeout` and `re-entry`, the last for an event that came from inside a mods API call your own hook made. From 2.1.301, those inner events skip your hook and your handler never hears about them. A `.catch` branch for `re-entry` is now dead code. A tool call your guard makes itself through `$.tool.call` won't be checked by that guard or by its `.catch`.

The check finds handlers that still test for it, then validates the mod:

```
grep -rn 're-entry' ./my-mod/hooks
claude plugin validate ./my-mod
```

Not every behavior change was filed under "Changed." In 2.1.294, `prompt` and `agent` hooks written as instructions, such as "Block commands that...", stopped allowing what they should block. In 2.1.299, `PreToolUse` and `PermissionRequest` hooks that set no shell stopped failing open on Windows when [Git for Windows](https://gitforwindows.org) isn't installed. In 2.1.300, an `agent` hook whose `model` is unavailable falls back to the caller's model instead of allowing the action ([changelog](https://code.claude.com/docs/en/changelog)). If a guard of yours never seemed to fire, it may start firing now.

I build Hermes IDE, so weigh this section with that in mind. Hermes runs Claude Code's own interface in a real terminal, so `/hooks`, `/plugin` and `claude --debug` behave the way they do anywhere else.

What Hermes adds is a place to test a hook change without touching your main checkout. ⌘N (Ctrl+Shift+N on Windows and Linux) starts each task on its own `hermes/<name>` branch in its own worktree. That worktree has its own copy of every tracked file, including `.claude/settings.json`, `.claude/skills/` and `.claude/agents/`. Edit a hook there, run the checks above in that session's terminal, and land it once it behaves. Two limits: hooks in `~/.claude/` are shared by every worktree, and git builds a worktree from tracked files, so check whether an untracked `.claude/settings.local.json` is there before you trust a test run.

The Commands list above each session shows pinned and built-in commands, and since 2.3.4 your own commands and skills too, each with a short description. That makes it a quick inventory of what items 1 and 2 touch.

*The action bar above a Claude Code session (Prompts, Context 4%, Model, Plan, Review, / Commands) with the Commands list open: pinned Context, Model, Plan and Review, then built-in commands such as Compact, Usage and Diff.*

Hermes never answers a permission prompt for you and never types into a terminal on its own, so it adds no decider of its own: your hooks, your permission rules and you still make the call. It is free, source-available under BSL 1.1, and runs on macOS, Windows and Linux. It isn't affiliated with Anthropic, Nous Research or any agent vendor.

The first two changes, and the 2.1.294 fix for `prompt` hooks, sit at one boundary: the prompt text that reaches the model, and who gets to rewrite it on the way in. Blocking a prompt, expanding a skill and writing a `prompt` hook as an instruction are all prompt engineering, done in a config file now. The [Prompt Engineering Pocket Guide](https://www.amazon.com/dp/B0GX38N645) covers the techniques underneath, from system prompts to prompt injection.

If you want to run the rechecks on a throwaway branch, [download Hermes IDE](https://hermes-ide.com/download), and if a step doesn't behave as described, [open an issue on GitHub](https://github.com/hermes-hq/hermes-ide).
