Summary
postToolUse hooks are not dispatched when the model uses the web_fetch
tool. This breaks the universal-interception promise of the hook system for
HTTP responses — which are often the largest single category by bytes in a
diagnostic session.
Repro
-
Install any working postToolUse hook. A minimal one for the test —
write ~/.copilot/hooks-bin/probe.sh:
#!/usr/bin/env bash
echo "[$(date -Iseconds)] post-tool fired with: $(cat | jq -c '.toolName')" >> ~/.copilot/hook-probe.log
echo '{}'
And ~/.copilot/hooks/probe.json:
{
"version": 1,
"hooks": {
"postToolUse": [
{ "type": "command", "bash": "/path/to/probe.sh" }
]
}
}
-
Run a session that mixes shell and web_fetch calls. For example:
copilot --log-level debug -p "Fetch https://api.github.com/repos/microsoft/typescript using web_fetch and also run 'date' in powershell. Summarize both."
-
After the session:
cat ~/.copilot/hook-probe.log
Expected
One […] post-tool fired with: "<tool>" line per successful tool
invocation, including the web_fetch calls — consistent with how the hook
fires for bash, powershell, view, grep, MCP-provided tool results,
etc.
Actual
Lines for powershell and any other tools used, but no lines for
web_fetch. The hook process is never spawned for web_fetch results.
Empirically confirmed by counting Executing hook.*post-tool lines in
~/.copilot/logs/process-*.log vs unique tool_use_ids in the same log.
In a captured session with 2 web_fetch calls and 33 other tool calls,
exactly 33 post-tool hook executions appeared. The absence is structural,
not conditional on resultType — even errored web_fetch results
(404s) get zero dispatches.
preToolUse does fire for web_fetch (35/35 in the same capture), so
the gap is specifically on the post-tool side.
Impact
HTTP responses fetched via web_fetch — often the largest single category
by bytes in real diagnostic sessions (probing internal APIs, fetching MCP
server status pages, GitHub API queries) — bypass any post-tool hook the
user has installed. That includes:
- Context-compression tools (e.g. coagula,
which surfaced this; the hook silently misses ~60% of bytes on
HTTP-heavy workflows)
- Audit/logging hooks for compliance
- PII or secret redactors
The current workaround is to nudge the model toward Invoke-RestMethod,
Invoke-WebRequest, or curl invoked through the powershell tool —
those go through the dispatched hook chain. But the model often prefers
web_fetch for HTTP work since it's the dedicated tool for that, and
prompting around tool preference isn't reliable.
Suggested fix
Dispatch postToolUse for web_fetch results the same way it's
dispatched for every other tool. The hook can then decide what to do
with the response (no-op, transform via modifiedResult, log, etc.) —
matching the pattern documented in the hooks reference for other tools.
Environment
Summary
postToolUsehooks are not dispatched when the model uses theweb_fetchtool. This breaks the universal-interception promise of the hook system for
HTTP responses — which are often the largest single category by bytes in a
diagnostic session.
Repro
Install any working
postToolUsehook. A minimal one for the test —write
~/.copilot/hooks-bin/probe.sh:And
~/.copilot/hooks/probe.json:{ "version": 1, "hooks": { "postToolUse": [ { "type": "command", "bash": "/path/to/probe.sh" } ] } }Run a session that mixes shell and
web_fetchcalls. For example:After the session:
cat ~/.copilot/hook-probe.logExpected
One
[…] post-tool fired with: "<tool>"line per successful toolinvocation, including the
web_fetchcalls — consistent with how the hookfires for
bash,powershell,view,grep, MCP-provided tool results,etc.
Actual
Lines for
powershelland any other tools used, but no lines forweb_fetch. The hook process is never spawned forweb_fetchresults.Empirically confirmed by counting
Executing hook.*post-toollines in~/.copilot/logs/process-*.logvs uniquetool_use_ids in the same log.In a captured session with 2
web_fetchcalls and 33 other tool calls,exactly 33 post-tool hook executions appeared. The absence is structural,
not conditional on
resultType— even erroredweb_fetchresults(404s) get zero dispatches.
preToolUsedoes fire forweb_fetch(35/35 in the same capture), sothe gap is specifically on the post-tool side.
Impact
HTTP responses fetched via
web_fetch— often the largest single categoryby bytes in real diagnostic sessions (probing internal APIs, fetching MCP
server status pages, GitHub API queries) — bypass any post-tool hook the
user has installed. That includes:
which surfaced this; the hook silently misses ~60% of bytes on
HTTP-heavy workflows)
The current workaround is to nudge the model toward
Invoke-RestMethod,Invoke-WebRequest, orcurlinvoked through thepowershelltool —those go through the dispatched hook chain. But the model often prefers
web_fetchfor HTTP work since it's the dedicated tool for that, andprompting around tool preference isn't reliable.
Suggested fix
Dispatch
postToolUseforweb_fetchresults the same way it'sdispatched for every other tool. The hook can then decide what to do
with the response (no-op, transform via
modifiedResult, log, etc.) —matching the pattern documented in the hooks reference for other tools.
Environment
https://docs.github.com/en/copilot/reference/hooks-configuration