Describe the bug
When more than one extension is active, every MCP host reload restarts the entire extension cohort. Each restarting extension calls session.resume on the same session. Tearing down an extension's SDK connection disposes the session's hook processor:
[DEBUG] [rust:hooks] disposing owned hook processor {"processor_id":"b28e360b-…","reason":"SDK connection removed"}
The hook processor appears to be owned by a single SDK connection rather than by the session. So once it is disposed, the next extension's session.resume fails and that extension dies during startup:
[extension-bootstrap] Failed to load extension: Error: Request session.resume failed with message:
GenericFailure, Hook processor is not configured for session id: <session-id>
=== peer-closed-before-ready ===
=== exit code=1 disposition=startup-failure ===
The CLI restarts the failed extension, which resumes again, which removes another connection — a restart loop.
If the final disposal leaves no hook processor registered, the session is left permanently without one. The next user prompt then fails:
[DEBUG] [rust:copilot_runtime::session::turn_user_message] userPromptSubmitted hook skipped:
GenericFailure, Hook processor is not configured for session id: <session-id>
In the TUI this surfaces as a hard failure and the submitted prompt is discarded:
✗ Execution failed: GenericFailure, Hook processor is not configured for session id: <session-id>
The session must be restarted, and there is no indication which component failed.
Affected version
GitHub Copilot CLI 1.0.81-9
Steps to reproduce the behavior
- Have several extensions active in one session. In my case four: one user extension (
~/.copilot/extensions/), one project extension (<repo>/.github/extensions/), and two contributed by an installed plugin. A sizeable MCP/plugin set makes it far more likely, because that is what drives the reloads (mine: 13 MCP servers, 12 plugins, 19 hooks).
- Start an interactive session — either fresh or with
--session-id=<uuid> / --resume=<uuid>.
- Watch the process log. Startup issues one
MCP host start (trigger=startup) followed by repeated MCP host reload requested (trigger=mcp_reload | plugin_change). I counted 6 reloads within 100 seconds of startup.
- Each reload relaunches every extension. In the extension log directory this appears as repeated launches of the same extensions with new PIDs, a few seconds apart. Over one ~100 s run: 14 extension launches for 4 extensions.
- Grep the process log for
disposing owned hook processor — it appears once per lost SDK connection.
- Wait for the reload storm to settle (~1 minute), then submit any prompt.
- The prompt fails with
Execution failed: GenericFailure, Hook processor is not configured for session id: ….
Timing matters, and I think it explains why this is intermittent: in two runs where I submitted the prompt at roughly t+20 s — before the disposals — the prompt succeeded and the log showed no userPromptSubmitted hook skipped. The failing run submitted at t+5 min, after the last disposal, and failed.
Expected behavior
- The hook processor should be scoped to the session, not to whichever SDK connection happened to create it. Losing one extension's connection should not remove hook processing for the session as a whole.
- Failing that, a subsequent
session.resume on a session with no hook processor should re-register one instead of returning GenericFailure.
- An extension that fails
session.resume should not enter an unbounded restart loop.
- A missing hook processor should degrade gracefully. The runtime already logs this as
userPromptSubmitted hook skipped at DEBUG — "skipped" implies non-fatal — yet the TUI reports it as a hard Execution failed and drops the user's prompt.
Additional context
Environment
- Windows 11, x86_64
- Windows Terminal, PowerShell 7
- copilot 1.0.81-9, installed via npm
- 13 MCP servers connected, 12 enabled plugins, 19 hooks
- 4 extensions: 1 user-level, 1 project-level, 2 plugin-contributed
Observed cascade, single process log (timestamps UTC):
18:46:44 Workspace initialized: <session-id> (checkpoints: 14)
18:46:50 loadDeferredRepoHooks(<session-id>): loaded repo hooks (hookCount=19)
18:46:50 MCP host start (trigger=startup, …)
18:46:53 MCP host reload requested (trigger=plugin_change)
18:46:53 MCP host reload requested (trigger=mcp_reload)
18:46:54 Received session.resume request: {"sessionId":"<session-id>", …} <- extension A
18:47:19 Received session.resume request: {"sessionId":"<session-id>", …} <- extension B
18:47:19 Cleaned up event forwarding for session <session-id>
18:47:24 Received session.resume request: {"sessionId":"<session-id>", …} <- extension C
18:47:24 Cleaned up event forwarding for session <session-id>
18:47:26 Received session.resume request: {"sessionId":"<session-id>", …} <- extension D
18:47:26 Cleaned up event forwarding for session <session-id>
18:47:29 [rust:hooks] disposing owned hook processor {"reason":"SDK connection removed"}
18:47:49 [rust:hooks] disposing owned hook processor {"reason":"SDK connection removed"}
…
18:51:57 userPromptSubmitted hook skipped: GenericFailure,
Hook processor is not configured for session id: <session-id>
Extension restart waves. All four extensions launch together, all reach === ready ===, all exit code=1, then relaunch. On the following wave some fail outright:
| time |
extension |
reached ready |
exit |
| 13:59:23 |
all 4 |
yes |
code=1 disposition=stopped-normally |
| 13:59:52 |
plugin ext |
no |
code=1 disposition=startup-failure (session.resume failed) |
| 13:59:52 |
project ext |
no |
code=1 disposition=startup-failure (session.resume failed) |
| 13:59:56 |
all 4 |
yes |
code=1 disposition=stopped-normally |
| 14:00:10 |
project ext |
no |
code=1 disposition=startup-failure (session.resume failed) |
Attempted workaround that did not help. Reducing the active extensions from 4 to 2 (disabling the plugin that contributes two of them) still produced 2 hook processor disposals in the same 100 s window. Fewer participants narrows the race but does not close it.
Possibly the same root cause as the repeated reload notices. The reload storm that drives these restarts also surfaces as several consecutive
MCP Servers reloaded: N servers connected
notices during startup, which is what first drew my attention. The message is emitted only when the reload reason is not startup; in my logs the extra reloads carry trigger=mcp_reload and trigger=plugin_change. If plugin/extension registration settled before the MCP host finished its initial start, both symptoms would likely disappear together.
One further detail that may be relevant: on the second MCP host start, the log shows pluginFingerprint=undefined, whereas the initial startup call carries a real fingerprint. If that fingerprint is what would normally short-circuit an unchanged reload, losing it may be what makes each reload do a full teardown and restart.
Describe the bug
When more than one extension is active, every MCP host reload restarts the entire extension cohort. Each restarting extension calls
session.resumeon the same session. Tearing down an extension's SDK connection disposes the session's hook processor:The hook processor appears to be owned by a single SDK connection rather than by the session. So once it is disposed, the next extension's
session.resumefails and that extension dies during startup:The CLI restarts the failed extension, which resumes again, which removes another connection — a restart loop.
If the final disposal leaves no hook processor registered, the session is left permanently without one. The next user prompt then fails:
In the TUI this surfaces as a hard failure and the submitted prompt is discarded:
The session must be restarted, and there is no indication which component failed.
Affected version
Steps to reproduce the behavior
~/.copilot/extensions/), one project extension (<repo>/.github/extensions/), and two contributed by an installed plugin. A sizeable MCP/plugin set makes it far more likely, because that is what drives the reloads (mine: 13 MCP servers, 12 plugins, 19 hooks).--session-id=<uuid>/--resume=<uuid>.MCP host start (trigger=startup)followed by repeatedMCP host reload requested (trigger=mcp_reload | plugin_change). I counted 6 reloads within 100 seconds of startup.disposing owned hook processor— it appears once per lost SDK connection.Execution failed: GenericFailure, Hook processor is not configured for session id: ….Timing matters, and I think it explains why this is intermittent: in two runs where I submitted the prompt at roughly t+20 s — before the disposals — the prompt succeeded and the log showed no
userPromptSubmitted hook skipped. The failing run submitted at t+5 min, after the last disposal, and failed.Expected behavior
session.resumeon a session with no hook processor should re-register one instead of returningGenericFailure.session.resumeshould not enter an unbounded restart loop.userPromptSubmitted hook skippedat DEBUG — "skipped" implies non-fatal — yet the TUI reports it as a hardExecution failedand drops the user's prompt.Additional context
Environment
Observed cascade, single process log (timestamps UTC):
Extension restart waves. All four extensions launch together, all reach
=== ready ===, all exitcode=1, then relaunch. On the following wave some fail outright:code=1 disposition=stopped-normallycode=1 disposition=startup-failure(session.resumefailed)code=1 disposition=startup-failure(session.resumefailed)code=1 disposition=stopped-normallycode=1 disposition=startup-failure(session.resumefailed)Attempted workaround that did not help. Reducing the active extensions from 4 to 2 (disabling the plugin that contributes two of them) still produced 2 hook processor disposals in the same 100 s window. Fewer participants narrows the race but does not close it.
Possibly the same root cause as the repeated reload notices. The reload storm that drives these restarts also surfaces as several consecutive
notices during startup, which is what first drew my attention. The message is emitted only when the reload reason is not
startup; in my logs the extra reloads carrytrigger=mcp_reloadandtrigger=plugin_change. If plugin/extension registration settled before the MCP host finished its initial start, both symptoms would likely disappear together.One further detail that may be relevant: on the second
MCP host start, the log showspluginFingerprint=undefined, whereas the initial startup call carries a real fingerprint. If that fingerprint is what would normally short-circuit an unchanged reload, losing it may be what makes each reload do a full teardown and restart.