vigiles API reference
    Preparing search index...

    Function matchesTool

    vigiles/hook — the closed vocabulary for authoring a compiled hook.

    A hook today is opaque shell (bash guard.sh): un-analyzable, and the author hand-writes the fragile parts (exit code, JSON field, a grep matcher) that cause the #1 verified hook pain — FALSE CONFIDENCE (a guardrail that looks like it blocks but silently doesn't). Invert it: author a pure typed function (event) => Decision against THIS surface, and vigiles compile emits the harness protocol for you. The whole false-confidence class becomes UNREPRESENTABLE — you never write the exit code or the field.

    The roles, each with its own output type so a category mistake is a tsc error, not a silent no-op:

    • experimental_defineHook / experimental_defineFileGate — a gate returns a Decision (allow/deny/ask); deny is the only thing that blocks.
    • experimental_definePromptGate — a prompt gate (UserPromptSubmit) sees the prompt TEXT and may deny to block it (a security filter).
    • experimental_defineStopGate — a stop gate (Stop/SubagentStop) may deny to keep the agent going (gate-until-tests-pass).
    • experimental_defineInject — an inject returns an Injection (context text); it has no deny, so "block on a SessionStart hook" won't compile.
    • experimental_defineReact — a react (PostToolUse) returns a Reaction; it sees the tool RESPONSE, its run(cmd) is effect-classified at construction, and it can't block (the tool already ran).

    Every gate takes a mode: enforce (default, blocks) or observe (the shadow/rollout mode — records what it WOULD block, never blocks). observe is harness-neutral (it just exits 0 + writes a local record).

    Matching is AST-backed (command.runs("git push", { force })), so it catches cd x && git push -f that the native Bash(git:*) glob misses.

    A gate may also decide on EXTERNAL STATE by declaring needs (e.g. needs: ["git.branch"]): the trusted runtime gathers those read-only facts and hands them in as e.ctx — the hook still does zero I/O, and reading an undeclared fact is a tsc error. Built-ins: git.branch/git.isDirty/git.root/cwd/os.platform/env.isCI. For a one-off off-catalog fact, the lightweight opt-out is an inline provide(name, cmd) (read-only) or dangerously(name, cmd) (the loud escape) right in needs. See research/hook-context-providers.md.

    ⚠️ Honest scope: compile/verify fix the hook's AUTHORING + LOGIC, not the harness's DELIVERY. The delivery floor MOVED — #34692 (a subagent's tool calls never reaching PreToolUse) is FIXED as of Claude Code 2.1.241, measured against a stock registry install and pinned by src/subagent-delivery.test.ts, which goes red if it regresses. SCOPE of that measurement: headless claude -p only — interactive is unmeasured, and depth-2 subagent nesting does not occur there at all. What has NOT changed: a model can still route around a tool entirely (#45427 / #32376 — a Bash heredoc instead of Write), so a gate is a strong default and is NEVER an unbypassable wall. See docs/compiled-hooks.md.

    • Does name match a hook's declared tool list, under the SAME semantics as the matcher the compiler emits for it?

      🔴 IT DID NOT, AND THE DISAGREEMENT WAS SILENT. hookRouting joins a react's tools with | and emits that as the harness matcher, and a Claude Code matcher is a REGEX — Edit|Write|MultiEdit only works because it is one. The runtime meanwhile compared with Array.includes, i.e. exact string equality. So a hook declaring a tool FAMILY compiled fine, was wired up fine, was routed to by the harness fine, and was then dropped by vigiles' own filter without a word.

      MEASURED 2026-08-12 against the real runtime, before the fix:

      $ echo '{"tool_name":"mcp__4f54037d-0499__list_events",…}'
      | vigiles hook-runtime run-program mcp-family.hook.mjs exit=0 # silence — react() never ran $ echo '{"tool_name":"mcp__.",…}' | … FIRED on mcp__. # fires only for a tool LITERALLY named "mcp__.*"

      That is the false-confidence class this whole subsystem exists to eliminate, living inside the subsystem. The live evidence that the harness really does route these: the knowledge base has shipped "matcher": "mcp__.*" in .claude/settings.json for months and its stamp file was last written the morning this was measured. The MCP server's id changes per session, so an exact list cannot be written down — a family matcher is the only correct spelling.

      Anchored ^(…)$ so a pattern cannot match a longer tool name by accident, and identical to includes for ordinary names, which contain no metacharacters. An unparseable pattern is rejected at COMPILE (invalidToolPatterns), so the fallback here is unreachable in a compiled hook and exists only so that a hand-constructed one degrades to exact matching rather than throwing mid-event.

      Parameters

      • tools: readonly string[]
      • name: string

      Returns boolean