Skillforge Field notes on shipping with AI tools

Claude Code Hooks: Bash Matchers and the Monitor Tool

claude-codehooks

If you have a PreToolUse hook with a Bash matcher that guards shell commands, Claude Code v2.1.314 or later also runs it on commands Claude starts through the Monitor tool. The catch is that on those commands your hook's allow and updatedInput have no effect. A deny still works. This page walks through what the hooks reference says, and how to change a hook that needs to allow or rewrite Monitor commands.

Verified against: the Claude Code hooks reference fetched on 2026-11-06. This is documentation reading, not a live run: we did not start a Monitor command under a hook for this page, so every behavior below is as documented, not as observed.

What the reference says

Quoted from the "Match Monitor commands" section of the hooks reference:

A PreToolUse or PermissionRequest hook with a Bash matcher also runs on shell commands Claude starts with the Monitor tool, but its allow and updatedInput have no effect on them. Requires Claude Code v2.1.314 or later.

It adds that matchers such as Bash|PowerShell, ^Bash$ and Bash.* count as Bash matchers. A Bash matcher hook does not run for a WebSocket watch, and a PostToolUse hook with a Bash matcher does not run for Monitor.

What each hook can do to a Monitor command

Your hookGets Monitor's inputCan deny or askallow and updatedInput apply
Bash matcheras a Bash callyesno
Matcher that includes Monitor (`Bash\Monitor, .*`, or none)Monitor's own inputyes

The reference states that a hook that runs for a Monitor command "can deny it or have Claude Code ask you about it". To allow or rewrite one, it says to switch from a Bash matcher to one that includes Monitor.

Practical consequences

Deny-style policy hooks need no change. A hook that blocks rm -rf or git push --force with exit code 2 now covers the same patterns when Claude starts them through Monitor. Per the reference, exit code 2 on a PreToolUse hook blocks the tool call.

Auto-approve and rewrite hooks do not extend. If your hook returns allow for safe read-only commands, or rewrites commands with updatedInput (for example to add a flag or a wrapper), those decisions are ignored on Monitor commands while the matcher is Bash. The Monitor command goes through the normal permission flow instead.

To handle both, name Monitor. Use a matcher like Bash|Monitor. Then, per the reference, the hook receives the Monitor call's own input and allow and updatedInput apply.

The if field

The if field takes a permission-rule pattern. The reference says:

  • In if, name Monitor, as in Monitor(tail *), which matches by the rules Bash(tail *) follows.
  • If a hook with a Monitor-including matcher has an if that names Bash, the hook still gets the Monitor call's input, but allow and updatedInput have no effect.
  • A hook with a Bash matcher and an if that names Monitor does not run.

So the matcher and the if must agree about the tool name, or the hook either skips or loses its decision power.

An example settings entry

This is a config excerpt following the documented structure; we did not run it. The script path is a placeholder for your own hook.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|Monitor",
        "hooks": [
          {
            "type": "command",
            "if": "Monitor(tail *)",
            "command": "/path/to/allow-tail.sh"
          }
        ]
      }
    ]
  }
}

With that if, the hook runs only for Monitor commands starting with tail. Drop the if to run it for every Bash and Monitor call.

Do not treat a hook as a hard gate

The reference describes if matching as best effort: it says to use the permission system, not a hook, for a hard allow or deny. It also says a timed-out command, http or mcp_tool hook on PreToolUse does not block the tool call unless it is one of the hooks whose failure blocks the action. For that, see Claude Code Hooks onFailure and the hooks guide.

FAQ

Do I need to update my hooks after upgrading to 2.1.314?

Only if a Bash hook returns allow or updatedInput and you want that to apply to Monitor commands. Deny hooks keep working and now also cover Monitor.

Does a PostToolUse hook with a Bash matcher run for Monitor?

Per the reference, no.

Does a Bash matcher run for a WebSocket watch?

Per the reference, no.

Is this version-sensitive?

Yes. The reference marks the behavior as requiring v2.1.314 or later. On older versions we have no documentation of what a Bash matcher does with Monitor, so check your version with claude --version first.