You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Plugin-defined preToolUse hooks (hooks.json) do not fire - neither in main session nor subagents #2540
preToolUse hooks defined in a plugin's hooks.json file are never executed. The hooks don't fire in the main agent session or in subagents spawned via the task tool.
This is distinct from #2392, which reports that config.json-defined hooks work in the main session but not subagents. In our case, plugin-sourced hooks don't fire anywhere.
Affected version
1.0.18 (macOS)
Steps to reproduce the behavior
Install a local plugin that defines a preToolUse hook in hooks.json:
The plugin's plugin.json references it with "hooks": "hooks.json"
The guardrails script outputs {"permissionDecision": "deny", ...} for matching commands (verified by running the script manually with piped JSON input)
Start a session in a project that has this plugin installed
Run a bash command that should be blocked (e.g., one containing ghe-config-apply)
Result: Command executes without any hook intervention
Manually running the same script with the same input correctly returns a deny decision
Expected behavior
Plugin-defined preToolUse hooks should be loaded and executed for all tool calls, the same way config.json-defined hooks are.
Additional context
macOS, zsh, Copilot CLI 1.0.18
Plugin installed via local path (source.path in config.json installed_plugins)
The plugin's skills load and work correctly - only hooks are not firing
Tested with two separate local plugins, both define preToolUse hooks in hooks.json, neither fires
+1 All hooks don't seem to work, pre and post skill use, for plugins installed via marketplace. The same hooks DO fire if the hooks directory is manually copied into the agent/repo cwd.
#3659 ("CLI cannot execute hooks shipped with plugins") looks like the same defect.@brandonh-msft traced it there: a plugin's hooks.json points at its script with a path relative to cwd: ".", but once installed the plugin lives under $COPILOT_HOME/cache/marketplaces/<repo-slug>/plugins/<plugin>. The CLI resolves the hook command against the project cwd rather than the plugin root, so the script isn't found and the hook never runs.
That explains why this looked like "hooks don't fire at all" on 1.0.18: back then a failed preToolUse hook was silently allowed. As of 1.0.57 those errors deny instead of silently passing (changelog 1.0.57), so the same failure now surfaces loudly in #3659 instead of looking like a no-op.
Workaround (works as of 1.0.60): set "cwd": "${PLUGIN_ROOT}" in the plugin's hooks.json, since cwd expansion of plugin vars landed in 1.0.60 (PLUGIN_ROOT itself has been injected since 1.0.26).
Default a plugin-sourced hook's cwd to its plugin root when cwd is unspecified, and resolve relative bash/powershell/File paths against PLUGIN_ROOT — plugin authors can't know the install path, so relative paths need to anchor to the plugin root by default (the way skills/commands in the same plugin already resolve).
Document PLUGIN_ROOT.
Suggest deduping against #3659, which carries the fuller diagnosis.
Still reproducing on 1.0.60 (42 patch releases after this was first reported on 1.0.18). And in our case, none of the 8 hook event types fire — not just preToolUse. Posting evidence in case it helps maintainers triage.
Environment
Copilot CLI version
1.0.60
OS / shell
macOS 25.5.0, zsh
Plugin source
Installed via copilot plugin install from a public marketplace repo
The bash command unwraps to a plain node script.mjs router invocation. Manually firing it works fine (returns proper {"hookSpecificOutput":{"permissionDecision":"deny",...}} JSON on stdout).
Evidence the hooks never fire
1. Manual invocation works
$ echo'{"toolName":"Read","toolArgs":{"path":"/tmp/x"},"sessionId":"sim"}'| \
CLAUDE_PLUGIN_ROOT=~/.copilot/installed-plugins/armoriq/armorcopilot \
COPILOT_HOOK_EVENT=PreToolUse \
node ~/.copilot/installed-plugins/armoriq/armorcopilot/scripts/bootstrap.mjs router
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny",
"permissionDecisionReason":"ArmorIQ intent drift: tool not in plan (Read)"}}
The hook returns deny when fed a representative payload.
2. Real Copilot sessions: zero hook activity in logs
grep -iE 'hookspec|hookresult|hook event|exec hook|spawn hook|preToolUse|userPromptSubmit' ~/.copilot/logs/process-*.log across all logs from 1.0.59 and 1.0.60 sessions on this machine returns zero matches.
Compare to MCP activity in the same logs, which is heavily logged:
2026-06-09T05:56:31.325Z [ERROR] Starting MCP client for armorcopilot-policy with...
2026-06-09T05:56:31.511Z [ERROR] MCP client for armorcopilot-policy connected, took 185ms
2026-06-09T05:56:55.567Z [ERROR] [mcp server armorcopilot-policy stderr] ArmorIQ SDK initialized...
2026-06-09T05:56:58.170Z [ERROR] [mcp server armorcopilot-policy stderr] Intent token issued...
MCP runs cleanly. Hooks are completely silent — not even an error indicating Copilot tried to spawn them.
3. Audit dir from plugin side stays empty
Our plugin appends a row to a local WAL on every hook fire (postToolUse). After a full session with Read / Edit / Bash tool calls, ~/.copilot/armorcopilot/audit/ has only an empty archive/ subdir. The plugin never received a hook event.
4. Plugin is correctly installed + enabled
$ copilot plugin list
Installed plugins:
• armorcopilot@armoriq (v0.1.0)
~/.copilot/config.json shows "enabled": true for the plugin.
Steps to reproduce
# 1. install the plugin (public repo, MIT)
copilot plugin marketplace add armoriq/armorCopilot
copilot plugin install armorcopilot@armoriq
# 2. open a session
copilot
# 3. inside the session, ask Copilot to do anything that should trigger preToolUse>read README.md and summarize it
# 4. observe: Read fires, no hook intervention, no log entries about hook execution
What works (so it's not a complete plugin failure)
The plugin's MCP server (armorcopilot-policy) starts cleanly, takes ~185ms to connect.
All MCP tools (register_intent_plan, policy_read, policy_update) are callable from chat.
MCP server stderr is logged at ERROR level so we can see SDK init, plan capture, intent token issuance.
Happy to provide a full debug-level log if you can point at a specific reproduction-time logging command — --log-level debug does not reveal any hook-attempt entries either.
Follow-up with stronger evidence: Copilot CLI 1.0.60's session-start banner explicitly confirms it discovers and loads all 8 hooks from our plugin's hooks.json — they're parsed, counted, and registered. They just never execute.
So the bug is even more narrowly scoped than the original issue suggested:
Stage
Status
Plugin discovery from installed-plugins/<marketplace>/<plugin>/
✅ works (1 plugin)
plugin.json parsing
✅ works
hooks.json parsing — 8 hooks counted, matches the 8 entries in our file
✅ works
MCP server registration
✅ works (1 MCP server)
Skills registration
✅ works (3 skills)
Hook executor invoking loaded hook entries during tool calls
❌ silently no-op
Copilot's runtime knows the hooks exist. It just doesn't fire them when tool calls happen. Same plugin's MCP server gets invoked normally (we see it in logs and chat) — so the issue is specific to the hook execution path, not plugin runtime in general.
Hopefully this helps narrow the search. Happy to provide more session-level state if useful.
userPromptSubmitted hook additionalContext injected into Copilot's planner
✅
❌
register_intent_plan MCP tool invoked every prompt (as our hook directs)
✅ — log shows Capturing plan: prompt=Show active security rules / prompt=Block all web fetches / prompt=... for every user message
❌ — fires only on the first prompt of a new session, never again
preToolUse enforcement on raw Read / Bash tool calls
✅ — fetch blocked by policy rule
❌ — Read / Bash / Edit run unblocked
1.0.60 published 2026-06-05 16:42 UTC, 31 minutes after our last working session. Auto-update between Friday afternoon and Monday introduced the regression.
Concrete logs from the 1.0.59 (working) session
2026-06-05T16:13:51 [stderr] Capturing plan: llm=github-copilot, prompt=Show active security rules
2026-06-05T16:14:10 [stderr] Capturing plan: llm=github-copilot, prompt=Block all web fetches
2026-06-05T16:15:12 [stderr] Capturing plan: llm=github-copilot, prompt=Show active security rules
Each Capturing plan line is stderr from the plugin's MCP server when register_intent_plan is called. On 1.0.59 it appeared for every user prompt (proving hook→additionalContext→planner→MCP tool was wired). On 1.0.60 it appears only once per session.
Workaround
Downgrade:
npm install -g @github/copilot@1.0.59
After downgrade, the same plugin + same flow works end-to-end again.
Suspect commits
Whichever PR between 1.0.59 and 1.0.60 touched:
hook output parsing (specifically hookSpecificOutput.additionalContext for userPromptSubmitted)
the planner's context-injection path
is the likely culprit. Happy to test specific candidate builds if you have prereleases between 1.0.59 and 1.0.60-0.
Thanks for the report. We've shipped fixes in this area since this was filed (most recently in Copilot CLI v1.0.96), and we believe this may now be resolved. Could you retest on the latest release (Copilot CLI v1.0.103) and let us know whether you still see it? If we don't hear back in a couple of weeks, we may close this issue — you can always reopen it.
Triaged as part of a Copilot SDK backlog review, with help from GitHub Copilot.
Activity
Smfakhoury commentedon Apr 23, 2026
+1 All hooks don't seem to work, pre and post skill use, for plugins installed via marketplace. The same hooks DO fire if the hooks directory is manually copied into the agent/repo cwd.
pycloux commentedon Apr 26, 2026
+1 seems to be the same for VSCode : microsoft/vscode#311238 (comment)
brunobastosg commentedon May 28, 2026
I have the same problem. In my case, the plugin has a
agentStophook, which is never fired.solvaholic commentedon Jun 8, 2026
Adding a likely root cause and a workaround.
#3659 ("CLI cannot execute hooks shipped with plugins") looks like the same defect. @brandonh-msft traced it there: a plugin's
hooks.jsonpoints at its script with a path relative tocwd: ".", but once installed the plugin lives under$COPILOT_HOME/cache/marketplaces/<repo-slug>/plugins/<plugin>. The CLI resolves the hook command against the project cwd rather than the plugin root, so the script isn't found and the hook never runs.That explains why this looked like "hooks don't fire at all" on 1.0.18: back then a failed
preToolUsehook was silently allowed. As of 1.0.57 those errors deny instead of silently passing (changelog 1.0.57), so the same failure now surfaces loudly in #3659 instead of looking like a no-op.Workaround (works as of 1.0.60): set
"cwd": "${PLUGIN_ROOT}"in the plugin'shooks.json, sincecwdexpansion of plugin vars landed in 1.0.60 (PLUGIN_ROOTitself has been injected since 1.0.26).Suggested fix, same as #3659's checklist:
cwdto its plugin root whencwdis unspecified, and resolve relativebash/powershell/Filepaths againstPLUGIN_ROOT— plugin authors can't know the install path, so relative paths need to anchor to the plugin root by default (the way skills/commands in the same plugin already resolve).PLUGIN_ROOT.Suggest deduping against #3659, which carries the fuller diagnosis.
Harihara04sudhan commentedon Jun 9, 2026
Still reproducing on 1.0.60 (42 patch releases after this was first reported on 1.0.18). And in our case, none of the 8 hook event types fire — not just
preToolUse. Posting evidence in case it helps maintainers triage.Environment
copilot plugin installfrom a public marketplace repoPlugin manifest
plugin.json(under.claude-plugin/):{ "name": "armorcopilot", "version": "0.1.0", "hooks": "./hooks/hooks.json", "mcpServers": "./.mcp.json" }hooks/hooks.json(all 8 event types declared):{ "version": 1, "hooks": { "sessionStart": [{"type":"command","bash":"node \"$CLAUDE_PLUGIN_ROOT/scripts/bootstrap.mjs\" router","env":{"COPILOT_HOOK_EVENT":"SessionStart"},"timeoutSec":30}], "userPromptSubmitted": [{...same shape, COPILOT_HOOK_EVENT: UserPromptSubmit ...}], "preToolUse": [{...PreToolUse...}], "permissionRequest": [{...PermissionRequest...}], "postToolUse": [{...PostToolUse...}], "postToolUseFailure": [{...PostToolUseFailure...}], "agentStop": [{...Stop...}], "sessionEnd": [{...SessionEnd...}] } }The bash command unwraps to a plain
node script.mjs routerinvocation. Manually firing it works fine (returns proper{"hookSpecificOutput":{"permissionDecision":"deny",...}}JSON on stdout).Evidence the hooks never fire
1. Manual invocation works
The hook returns deny when fed a representative payload.
2. Real Copilot sessions: zero hook activity in logs
grep -iE 'hookspec|hookresult|hook event|exec hook|spawn hook|preToolUse|userPromptSubmit' ~/.copilot/logs/process-*.logacross all logs from 1.0.59 and 1.0.60 sessions on this machine returns zero matches.Compare to MCP activity in the same logs, which is heavily logged:
MCP runs cleanly. Hooks are completely silent — not even an error indicating Copilot tried to spawn them.
3. Audit dir from plugin side stays empty
Our plugin appends a row to a local WAL on every hook fire (postToolUse). After a full session with Read / Edit / Bash tool calls,
~/.copilot/armorcopilot/audit/has only an emptyarchive/subdir. The plugin never received a hook event.4. Plugin is correctly installed + enabled
~/.copilot/config.jsonshows"enabled": truefor the plugin.Steps to reproduce
What works (so it's not a complete plugin failure)
armorcopilot-policy) starts cleanly, takes ~185ms to connect.register_intent_plan,policy_read,policy_update) are callable from chat.Only the hook system is silently no-op.
Public reproducer
Full plugin source: https://github.com/armoriq/armorCopilot
Just the hooks.json: https://github.com/armoriq/armorCopilot/blob/main/plugins/armorcopilot/hooks/hooks.json
Happy to provide a full debug-level log if you can point at a specific reproduction-time logging command —
--log-level debugdoes not reveal any hook-attempt entries either.Harihara04sudhan commentedon Jun 9, 2026
Follow-up with stronger evidence: Copilot CLI 1.0.60's session-start banner explicitly confirms it discovers and loads all 8 hooks from our plugin's hooks.json — they're parsed, counted, and registered. They just never execute.
Banner at session start:
So the bug is even more narrowly scoped than the original issue suggested:
installed-plugins/<marketplace>/<plugin>/1 plugin)plugin.jsonparsinghooks.jsonparsing —8 hookscounted, matches the 8 entries in our file1 MCP server)3 skills)Copilot's runtime knows the hooks exist. It just doesn't fire them when tool calls happen. Same plugin's MCP server gets invoked normally (we see it in logs and chat) — so the issue is specific to the hook execution path, not plugin runtime in general.
Hopefully this helps narrow the search. Happy to provide more session-level state if useful.
Harihara04sudhan commentedon Jun 9, 2026
Bisected to a specific regression: working on 1.0.59, broken on 1.0.60.
We have logs from the same machine + same plugin + same prompts on both versions, 31 minutes apart on the same day:
(2026-06-05 16:11 UTC)
(today, 2026-06-09)
Loading: 8 hooks, 3 skills, 1 MCP server, 1 pluginuserPromptSubmittedhookadditionalContextinjected into Copilot's plannerregister_intent_planMCP tool invoked every prompt (as our hook directs)Capturing plan: prompt=Show active security rules/prompt=Block all web fetches/prompt=...for every user messagepreToolUseenforcement on raw Read / Bash tool calls1.0.60 published 2026-06-05 16:42 UTC, 31 minutes after our last working session. Auto-update between Friday afternoon and Monday introduced the regression.
Concrete logs from the 1.0.59 (working) session
Each
Capturing planline is stderr from the plugin's MCP server whenregister_intent_planis called. On 1.0.59 it appeared for every user prompt (proving hook→additionalContext→planner→MCP tool was wired). On 1.0.60 it appears only once per session.Workaround
Downgrade:
After downgrade, the same plugin + same flow works end-to-end again.
Suspect commits
Whichever PR between 1.0.59 and 1.0.60 touched:
hookSpecificOutput.additionalContextforuserPromptSubmitted)is the likely culprit. Happy to test specific candidate builds if you have prereleases between 1.0.59 and 1.0.60-0.
feat(copilot): full harness parity with Claude Code + parity test suite
feat(ravenclaude-core): cause-taxonomy phases 1-11 + P1-3 must-fail t…
Add the Copilot Agent Plugins 1.0 marketplace bundle (R2)
Record where write-confinement is enforced, and why it is prompt-level
cli: record what was installed, and plant a plugin's Copilot hooks
cli: record what was installed, and plant a plugin's Copilot hooks
patniko commentedon Oct 26, 2026
Thanks for the report. We've shipped fixes in this area since this was filed (most recently in Copilot CLI v1.0.96), and we believe this may now be resolved. Could you retest on the latest release (Copilot CLI v1.0.103) and let us know whether you still see it? If we don't hear back in a couple of weeks, we may close this issue — you can always reopen it.
Triaged as part of a Copilot SDK backlog review, with help from GitHub Copilot.