Since Claude Code 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). 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, 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 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).
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). 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 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:
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). Cowork can run tasks in the cloud, on Anthropic's servers. 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 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 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 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 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). 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 covers the techniques underneath, from system prompts to prompt injection.
If you want to run the rechecks on a throwaway branch, download Hermes IDE, and if a step doesn't behave as described, open an issue on GitHub.