Summary
With a plugin installed from a local directory marketplace, its hook commands run in a fresh session but not in a resumed one. Starting a session with --session-id runs the hooks; resuming that same session with --resume=<id> runs none of them, for the rest of the session's life.
Everything below is black-box observation on a single machine, changing one variable at a time. I have not looked at the cause.
Versions
| Version |
Behavior |
| 1.0.81-1 |
hooks run in both a fresh and a resumed session |
| 1.0.81-2 |
same |
| 1.0.81-3 … 1.0.81-7 |
not tested |
| 1.0.81-8 |
hooks do not run in a resumed session |
| 1.0.81-9 |
same |
| 1.0.81-11 |
same |
OS: Windows 11, PowerShell 7. Verified by running the same probe plugin against 1.0.81-1 and 1.0.81-11 on the same machine — the only variable changed was the CLI binary.
Note when bisecting: the CLI auto-updates on startup and will overwrite a pinned npm install @github/copilot@<version> in place, which makes a version A/B silently compare a version against itself. --no-auto-update / COPILOT_AUTO_UPDATE=false is required, and it is worth asserting on the version the run actually logged.
Reproduction
Self-contained. Creates a throwaway local directory marketplace with one plugin whose only hook appends a line to a file. The hook body has no dependencies, so a line can only appear if the CLI started the command.
$ErrorActionPreference = 'Stop'
$root = Join-Path $env:TEMP 'hookrepro'
Remove-Item $root -Recurse -Force -ErrorAction SilentlyContinue
# Forward slashes: the path is embedded in JSON, where "\U" etc. is not a valid escape.
$marker = (Join-Path $root 'fired.txt').Replace('\', '/')
New-Item -ItemType Directory (Join-Path $root 'demo-hooks\hooks') -Force | Out-Null
New-Item -ItemType Directory (Join-Path $root '.github\plugin') -Force | Out-Null
New-Item -ItemType File $marker -Force | Out-Null
Set-Content (Join-Path $root '.github\plugin\marketplace.json') @"
{ "name": "demo-market", "owner": { "name": "demo" },
"plugins": [ { "name": "demo-hooks", "version": "0.0.1", "source": "./demo-hooks" } ] }
"@
Set-Content (Join-Path $root 'demo-hooks\plugin.json') @"
{ "name": "demo-hooks", "version": "0.0.1", "hooks": "hooks/hooks.json" }
"@
Set-Content (Join-Path $root 'demo-hooks\hooks\hooks.json') @"
{ "hooks": { "UserPromptSubmit": [ { "matcher": ".*", "hooks": [
{ "type": "command", "timeoutSec": 10,
"powershell": "Add-Content -LiteralPath '$marker' -Value 'fired'; exit 0" } ] } ] } }
"@
copilot plugin marketplace add $root
copilot plugin install demo-hooks@demo-market
$sid = [guid]::NewGuid().ToString()
copilot --no-auto-update --allow-all-tools --session-id $sid -p 'say hi' | Out-Null
"fresh -> hook fired $((Get-Content $marker | Measure-Object).Count) time(s)"
copilot --no-auto-update --allow-all-tools --resume=$sid -p 'say hi again' | Out-Null
"resume -> hook fired $((Get-Content $marker | Measure-Object).Count) time(s) total"
copilot plugin uninstall demo-hooks@demo-market
copilot plugin marketplace remove demo-market
Expected
fresh -> hook fired 1 time(s)
resume -> hook fired 2 time(s) total
Actual on 1.0.81-8 and later
fresh -> hook fired 1 time(s)
resume -> hook fired 1 time(s) total
On 1.0.81-1 the same script prints the expected output.
A prompt is submitted and answered in both runs, so UserPromptSubmit is expected in both. It is observed only in the fresh run.
What was measured
Using a probe plugin declaring seven hook events, where each hook writes a file recording its event name, its PID, and the session id it received on stdin:
=== FRESH SESSION (session 12cbfa43) ===
hook commands started : 4
SessionStart, UserPromptSubmit, Stop, SessionEnd
=== RESUMED SESSION (session 12cbfa43) ===
hook commands started : 0
Same binary, same plugin, same hook definitions, same session id. PreToolUse, PostToolUse and Notification did not run in either case, which is expected for a prompt that uses no tools and raises no notification.
For whatever it is worth to whoever picks this up, the CLI's own log at --log-level all reports a different hook count for the two runs, while reporting the same plugin discovery result:
fresh: [DEBUG] Plugin activation [agents]: fingerprint=b1da9033d7a0, plugins=2, loaded=0
[DEBUG] loadDeferredRepoHooks(896fb99e-f0f2-4cdc-98e6-256756c810bd): loaded repo hooks (hookCount=17)
resume: [DEBUG] Plugin activation [agents]: fingerprint=6613ac1c406c, plugins=2, loaded=0
[DEBUG] loadDeferredRepoHooks(896fb99e-f0f2-4cdc-98e6-256756c810bd): loaded repo hooks (hookCount=5)
That number moves one-for-one with the number of hook events the probe plugin declares — measured at 0, 1, 3 and 7 events, giving 10, 11, 13 and 17 against a baseline of 10 on this machine. I am not going to guess what it means internally.
Scope: which plugins are affected
Same plugin, same hook definition, same machine, CLI 1.0.81-11 — only the way the plugin is loaded changes:
| How the plugin is loaded |
Section in copilot plugin list |
Hooks run after resume |
| Path-sourced plugin in a directory-source marketplace |
Live Plugins (loaded from a local marketplace directory, never copied) |
no |
Installed from a GitHub repo, copied into ~/.copilot/installed-plugins |
Installed plugins |
yes |
--plugin-dir |
External Plugins (via --plugin-dir) |
yes |
For the copied plugin the marker file goes 1 after the fresh session and 2 after the resume; for the live path-sourced plugin it stays at 1.
So this is not marketplace plugins in general — copied marketplace and repo plugins are unaffected.
1.0.81-8 is also the release whose notes record:
Path-sourced plugins in a local (directory-source) marketplace now load live from their real directory, so editing one takes effect on /restart or a new session — no /plugin update
I am reporting that only as a timing coincidence worth checking, not as a diagnosis.
Secondary symptom that makes this hard to recognize
In an interactive resume the CLI first boots a throwaway session before switching to the resumed one, and that throwaway session does run the plugin's hooks. Its SessionEnd fires after the resumed session is already active and carries the throwaway session's id, not the resumed one.
An external process watching hook output therefore sees exactly one event, tagged with a session id it has never seen, and then nothing at all. That reads as "resume only emits SessionEnd" and sends you looking in the wrong place.
Impact
Any plugin that uses hooks for session lifecycle tracking, telemetry, notifications, or external integration silently stops working the moment a user resumes a session, with no error and no warning. A local directory marketplace is the natural way to ship hooks alongside an application, and --continue / --resume is the normal way to pick work back up, so the combination is common and the failure is invisible from inside the CLI.
Summary
With a plugin installed from a local directory marketplace, its hook commands run in a fresh session but not in a resumed one. Starting a session with
--session-idruns the hooks; resuming that same session with--resume=<id>runs none of them, for the rest of the session's life.Everything below is black-box observation on a single machine, changing one variable at a time. I have not looked at the cause.
Versions
OS: Windows 11, PowerShell 7. Verified by running the same probe plugin against 1.0.81-1 and 1.0.81-11 on the same machine — the only variable changed was the CLI binary.
Reproduction
Self-contained. Creates a throwaway local directory marketplace with one plugin whose only hook appends a line to a file. The hook body has no dependencies, so a line can only appear if the CLI started the command.
Expected
Actual on 1.0.81-8 and later
On 1.0.81-1 the same script prints the expected output.
A prompt is submitted and answered in both runs, so
UserPromptSubmitis expected in both. It is observed only in the fresh run.What was measured
Using a probe plugin declaring seven hook events, where each hook writes a file recording its event name, its PID, and the session id it received on stdin:
Same binary, same plugin, same hook definitions, same session id.
PreToolUse,PostToolUseandNotificationdid not run in either case, which is expected for a prompt that uses no tools and raises no notification.For whatever it is worth to whoever picks this up, the CLI's own log at
--log-level allreports a different hook count for the two runs, while reporting the same plugin discovery result:That number moves one-for-one with the number of hook events the probe plugin declares — measured at 0, 1, 3 and 7 events, giving 10, 11, 13 and 17 against a baseline of 10 on this machine. I am not going to guess what it means internally.
Scope: which plugins are affected
Same plugin, same hook definition, same machine, CLI 1.0.81-11 — only the way the plugin is loaded changes:
copilot plugin listLive Plugins (loaded from a local marketplace directory, never copied)~/.copilot/installed-pluginsInstalled plugins--plugin-dirExternal Plugins (via --plugin-dir)For the copied plugin the marker file goes
1after the fresh session and2after the resume; for the live path-sourced plugin it stays at1.So this is not marketplace plugins in general — copied marketplace and repo plugins are unaffected.
1.0.81-8 is also the release whose notes record:
I am reporting that only as a timing coincidence worth checking, not as a diagnosis.
Secondary symptom that makes this hard to recognize
In an interactive resume the CLI first boots a throwaway session before switching to the resumed one, and that throwaway session does run the plugin's hooks. Its
SessionEndfires after the resumed session is already active and carries the throwaway session's id, not the resumed one.An external process watching hook output therefore sees exactly one event, tagged with a session id it has never seen, and then nothing at all. That reads as "resume only emits SessionEnd" and sends you looking in the wrong place.
Impact
Any plugin that uses hooks for session lifecycle tracking, telemetry, notifications, or external integration silently stops working the moment a user resumes a session, with no error and no warning. A local directory marketplace is the natural way to ship hooks alongside an application, and
--continue/--resumeis the normal way to pick work back up, so the combination is common and the failure is invisible from inside the CLI.