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
Extensions with joinSession hooks override/disable hooks.json hooks #2076
When a CLI extension registers hooks via joinSession (e.g., onPreToolUse, onPostToolUse), the hooks previously defined in hooks.json stop firing. The extension hooks appear to replace/override the hooks.json pipeline rather than composing with it.
Expected: Extension hooks and hooks.json hooks should both run — extensions should layer on top of existing hooks, not replace them.
Actual: Once an extension with joinSession hooks is loaded, the hooks defined in hooks.json are no longer honored.
Affected version
GitHub Copilot CLI 1.0.5
Steps to reproduce the behavior
Create a hooks.json (or .github/hooks/hooks.json) with a hook — e.g., an onPreToolUse hook that logs or blocks a specific command.
Verify the hook fires as expected (it should).
Create a CLI extension in .github/extensions/ that calls joinSession with its own hooks (e.g., onPreToolUse or onPostToolUse).
Start a new Copilot CLI session so the extension loads.
Trigger the same action that would fire the hooks.json hook.
Observe: The hooks.json hook no longer fires. Only the extension's hooks run.
Expected behavior
Both the hooks.json hooks and the extension hooks should fire. Extensions should compose with existing hooks rather than replacing them. The hook pipeline should be additive — an extension registering onPreToolUse should not prevent a hooks.json onPreToolUse from also executing.
Additional context
This is consistently reproducible.
Reported via the GitHub Copilot at Microsoft Teams channel.
So the last extension to call updateOptions wins all previously registered extension hooks are silently lost.
Why It Only Affects Extensions (Not hooks.json)
A merge function Uxt() exists in the codebase and is used to compose hooks from hooks.json config files. But this merge is not applied when extensions register hooks via updateOptions. The fix should apply the same merge logic to extension hooks.
where r sends a hooks.invoke RPC to that specific extension's connection. When overwritten by the next extension's proxy, those RPC callbacks are lost the extension never receives hook invocations.
Workaround
session.on("user.message") + session.send() event listeners are registered per-connection and are not subject to the overwrite. This is what I'm using in my extension now.
Related
I filed #2142 for a related but separate issue: onSessionStart's additionalContext return value is also silently ignored (fire-and-forget invocation with no result capture).
Activity
shsolomo commentedon Mar 19, 2026
Root Cause Analysis
I hit this same bug and traced through the CLI source (v1.0.8). Here's the exact code path:
The Overwrite
In
initializeSession(), each extension's hooks are registered viaupdateOptions:The base
updateOptionsdoes direct assignment:So the last extension to call
updateOptionswins all previously registered extension hooks are silently lost.Why It Only Affects Extensions (Not hooks.json)
A merge function
Uxt()exists in the codebase and is used to compose hooks fromhooks.jsonconfig files. But this merge is not applied when extensions register hooks viaupdateOptions. The fix should apply the same merge logic to extension hooks.The Proxy Architecture
createHooksProxy(I, r)returns per-connection hook arrays like:where
rsends ahooks.invokeRPC to that specific extension's connection. When overwritten by the next extension's proxy, those RPC callbacks are lost the extension never receives hook invocations.Workaround
session.on("user.message")+session.send()event listeners are registered per-connection and are not subject to the overwrite. This is what I'm using in my extension now.Related
I filed #2142 for a related but separate issue:
onSessionStart'sadditionalContextreturn value is also silently ignored (fire-and-forget invocation with no result capture).fix: bypass CLI hook overwrite bug with session.on() boot injection
fix: bypass CLI hook overwrite bug with session.on() boot injection
fix: migrate broken hooks to session.on() event listeners
MRayermannMSFT commentedon Mar 23, 2026
Fix gone out in latest CLI pre-release