fix: Get hooks working in Copilot again - #2719
Conversation
The different coding agents (VS Code, Copilot, Claude etc.) have varying levels of support for the [Open Plugins spec](https://open-plugins.com/) and varying fallback mechanisms to try and support plugins designed for other coding agents. This makes for a bit of a mess when it comes to supporting hooks. For one thing, all the coding agents (or at least VS Code, Copilot CLI, and Claude) agree that hooks/hooks.json is a default location to find hooks. However, they do not agree on the format of that file. Further, they do not agree on a mechanism for excluding that file from the set of hooks files they read. VS Code attempts to solve this by trying to determine if a plugin is following the Open Plugin spec, the Copilot spec, or the Claude spec, and varying its behavior accordingly. For example, for an "open" plugin it defines the `PLUGIN_ROOT` variable; for Claude `CLAUDE_PLUGIN_ROOT`, and for Copilot plugins... neither. However, Copilot itself defines both `PLUGIN_ROOT` _and_ `CLAUDE_PLUGIN_ROOT` at all times. So a "Copilot" plugin may not work in VS Code, even though it is explicitly trying to make it work the same way. Here's where we currently are regarding hooks: - In VS Code they work. It picks up the .plugin/plugin.json file, causing it to treat this as an "open" plugin. It honors using hooks/hooks.json as the exclusive source of hook. - In Copilot hooks don't work at all. It, too, picks up .plugin/plugin.json but it doesn't understand the structure of the `hooks` section at all (it doesn't support the `exclusive` annotation) and so loads no hooks. - Claude sort of works. It will pick up .claude-plugin/plugin.json, and since that does not specify a hooks file it will use hooks/hooks.json as the default. However, spaces anywhere in the expanded `${CLAUDE_PLUGIN_ROOT}` will break the call to track-telemetry.sh. Here we make changes so that VS Code and Copilot will continue to use .plugin/plugin.json, but we remove the `hooks` section entirely. They will both fall back to the default of hooks/hooks.json. VS Code will use the `windows`, `osx` and `linux` commands, while Copilot will use `bash` and `powershell` instead. Claude is pointed to hooks/claude-hooks.json which is in the format it expects and adds in the necessary double-quotes to prevent whitespace issues. Note that it will still try to load hooks/hooks.json as well and and raise an error about it not being in the right format; this seems to be harmless. Also note that Claude provides no good way to automatically varying behavior between operating systems, so the telemetry reporting effectively only works on system where "bash" is installed (effectively every Mac and Linux system, but only some Windows systems). Experimentally this allows VS Code and Copilot to report telemetry on Windows systems. I am unable to test Claude fully but it does "see" the hook in claude-hooks.json.
There was a problem hiding this comment.
Pull request overview
This PR updates plugin hook configuration to restore telemetry hooks across different coding agents by aligning each agent’s manifest and hook-file format expectations.
Changes:
- Simplifies
plugin/.plugin/plugin.jsonby removing thehookssection so clients fall back to the defaulthooks/hooks.json. - Reworks
plugin/hooks/hooks.jsoninto a single file intended to work for both VS Code (OS-specific keys) and Copilot (shell-specific keys). - Adds a Claude-specific hooks file (
plugin/hooks/claude-hooks.json) and points the Claude manifest at it; removes the old Copilot-specific hooks file.
Show a summary per file
| File | Description |
|---|---|
| plugin/hooks/hooks.json | Restructures the default hooks definition to include OS- and shell-specific command entries. |
| plugin/hooks/copilot-hooks.json | Removes the Copilot-specific hooks file (intended to rely on default hooks discovery). |
| plugin/hooks/claude-hooks.json | Adds a Claude-specific hooks file and command string intended to handle whitespace in paths. |
| plugin/.plugin/plugin.json | Removes explicit hooks config to trigger default hooks file fallback behavior. |
| plugin/.claude-plugin/plugin.json | Configures Claude to use the new Claude-specific hooks file. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Low
Rick Winter (RickWinter)
left a comment
There was a problem hiding this comment.
Copilot looks to be using claude schema.
Rick Winter (RickWinter)
left a comment
There was a problem hiding this comment.
README is stale and needs updating with this PR
Jon Gallant (jongio)
left a comment
There was a problem hiding this comment.
The approach here is well-reasoned and the PR description does a great job explaining the compatibility matrix.
Two items:
-
The hooks compatibility table in README.md (line 137-138) will be stale after this lands. Copilot CLI row still references
plugin/hooks/copilot-hooks.jsonand Claude Code row sayshooks/hooks.json (no custom hooks). Both are now wrong. Worth updating here or in a quick follow-up. -
Nit:
claude-hooks.jsonis missing a trailing newline.
Rick Winter (@RickWinter) In what sense? |
Update the Claude `command` for `PostToolUse` to explicitly include "bash"; this avoids the quotes becoming part of the executable name as well as any potential issues with the .sh script being executable or not.
Jon Gallant (jongio)
left a comment
There was a problem hiding this comment.
Verified the full changeset. The path quoting asymmetry between hooks.json (unquoted ) and claude-hooks.json (quoted ) is correct: Copilot and VS Code resolve paths internally via structured keys, while Claude passes the command value to a shell as-is, requiring explicit quotes and the bash prefix. No blocking issues.
The different coding agents (VS Code, Copilot, Claude etc.) have varying levels of support for the Open Plugins spec and varying fallback mechanisms to try and support plugins designed for other coding agents. This makes for a bit of a mess when it comes to supporting hooks.
For one thing, all the coding agents (or at least VS Code, Copilot CLI, and Claude) agree that hooks/hooks.json is a default location to find hooks. However, they do not agree on the format of that file. Further, they do not agree on a mechanism for excluding that file from the set of hooks files they read.
VS Code attempts to solve this by trying to determine if a plugin is following the Open Plugin spec, the Copilot spec, or the Claude spec, and varying its behavior accordingly. For example, for an "open" plugin it defines the
PLUGIN_ROOTvariable; for ClaudeCLAUDE_PLUGIN_ROOT, and for Copilot plugins... neither. However, Copilot itself defines bothPLUGIN_ROOTandCLAUDE_PLUGIN_ROOTat all times. So a "Copilot" plugin may not work in VS Code, even though it is explicitly trying to make it work the same way.Here's where we currently are regarding hooks:
hookssection at all (it doesn't support theexclusiveannotation) and so loads no hooks.${CLAUDE_PLUGIN_ROOT}will break the call to track-telemetry.sh.Here we make changes so that VS Code and Copilot will continue to use .plugin/plugin.json, but we remove the
hookssection entirely. They will both fall back to the default of hooks/hooks.json. VS Code will use thewindows,osxandlinuxcommands, while Copilot will usebashandpowershellinstead.Claude is pointed to hooks/claude-hooks.json which is in the format it expects and adds in the necessary double-quotes to prevent whitespace issues. Note that it will still try to load hooks/hooks.json as well and and raise an error about it not being in the right format; this seems to be harmless.
Also note that Claude provides no good way to automatically varying behavior between operating systems, so the telemetry reporting effectively only works on system where "bash" is installed (effectively every Mac and Linux system, but only some Windows systems).
Experimentally this allows VS Code and Copilot to report telemetry on Windows systems. I am unable to test Claude fully but it does "see" the hook in claude-hooks.json.
Description
Checklist
cd tests && npm test)fix:,feat:,feature:,chore:,misc:,test:,eval:tests/,npm run test:integration -- <skill>ornpm run test:vally -- --skill <skill>)Related Issues