Skip to content

hooks.json: PostToolUse hook is unscoped — spawns a process on every tool call in every session #34

Open

Description

The PostToolUse entry in plugins/upgrade-agent/hooks.json declares no matcher or
scope, so it is invoked after every tool call, in every session, under every agent —
including sessions and agents unrelated to upgrades.

Environment

  • Plugin: upgrade-agent 1.1.404 (commit a4f718e)
  • Installed via the ms-dotnettools.upgrade-agent VS Code extension 1.1.290
  • Host: VS Code 1.133.0, Copilot Chat
  • OS: macOS 26.6 (25G72), arm64

Observed

A plain chat session — default agent, no upgrade context — asking "read plan.md and
summarize it" fires the hook on each file-search call.

Additional symptom: the chat transcript is polluted

The unconditional invocation is also directly user-visible. Because the hook runs on
every tool call, its output is interleaved with the actual work on every step. A
trivial two-step request produces:

Image

Half the visible steps are hook noise. This scales linearly with tool use, so longer
agentic runs become hard to read, and the warnings look alarming despite being
completely inert — the script always returns {"continue":true} and never blocks the
host call. Users have no way to tell they're harmless, or that they come from a plugin
unrelated to what they're currently doing.

The two defects compound here: # makes each invocation fail loudly, and the missing
scope makes it invoked everywhere. Fixing # alone silences the transcript but leaves
the spawn cost described above; adding scope alone reduces the noise to file-read calls
only. Both are worth fixing.

Separately, it may be worth considering whether a non-blocking hook failure should
surface in the user-facing transcript at all, rather than in a log — that would protect
users from any misbehaving third-party hook, not just this one.

Why this matters

All filtering happens inside the script, after the process has already spawned:

TOOL_NAME=$(extract_field "$INPUT" tool_name toolName)   # spawns jq or python3
case "$TOOL_NAME" in
    read_file|view|Read) ;;
    *) write_success ;;                                   # ...only to discard
esac

An unrelated tool call therefore costs a bash spawn plus a jq/python3 spawn purely
to determine the hook isn't interested. Per tool call, in every session, indefinitely.

The whitelist is static and known ahead of time (read_file, view, Read), so this
decision could be made before any process is created.

To be clear

The script itself is well behaved: it never blocks the host tool call, makes no network
requests, and records only the names of its own SKILL.md loads to a temp directory. It
does not capture user code, file contents, or prompts. The concern is solely that the
invocation is unconditional when the filtering criteria are static.

Suggested fix

Scope the hook at the manifest level so the runner can skip it without spawning a
process — e.g. restrict it to the file-read tools the script already whitelists.

If the current hooks schema has no such mechanism, this may belong with #29
(Adopt Agent Plugins 1.0).

Related

See: #33
Fixing that will make this one's cost real rather than theoretical.

Activity

  1. lonitra commented on Aug 27, 2026

    @lonitra
    Member

    Thank you for reporting this issue!

    After investigating this, we found that a matcher field can be used to scope hooks to specific tools. Unfortunately, VS Code silently discards the matcher field during hook parsing, so adding "matcher": "Read" to our hooks.json will have no effect today in VS Code as it strips matcher out and runs all hooks for every tool call regardless.

    I've filed this upstream: microsoft/vscode#332988

    The Copilot CLI on the other hand does honor the matcher field currently, so we'll update it so that it doesn't fire for all tools at least in CLI. Once the VS Code upstream fix lands, I'll double check that everything works properly before closing out this issue. In the meantime, a fix for #33 is already merged and pending release, so this should help with the chat pollution at least until upstream is resolved.

  2. lonitra commented on Sep 15, 2026

    @lonitra
    Member

    The matcher has been added to the hooks as of plugin version 1.1.485

    This is resolved in CLI. For VSCode, based on upstream issue, there looks to be no plans to honor the matcher in the native VS Code agent hooks implementation. This native VSCode agent will eventually be deprecated and superseded by the shared Copilot SDK implementation. Until then the recommended workaround for this is to select 'Copilot' as the session target - this will honor the matcher:
    Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions