[BUG] Marketplace plugin re-materialization mid-session silently kills pinned hooks in every running session — and the failure is undetectable from inside the plugin
[BUG] Marketplace plugin re-materialization mid-session silently kills pinned hooks in every running session — and the failure is undetectable from inside the plugin
Re-file of #73952
This re-files #73952, which was auto-closed as stale (not planned) on 2026-08-26 after ~8 weeks with no maintainer response. The bug is unchanged and still reproduces. Re-filing per the stale-bot's own instruction, and adding: (1) a verified detection mechanism, (2) an independent community confirmation, and (3) a concrete note on why in-plugin mitigation is structurally impossible.
Summary
When a marketplace plugin updates mid-session (version bump → marketplace re-materializes the plugin cache dir, removing the old versioned install path), a running session's hooks stay pinned to the previous version's path. Re-materialization removes that path, so the pinned hook commands become unreachable and silently stop firing — no error, no warning, session otherwise normal. Every session live at the moment of re-materialization loses that plugin's hooks at once. Recovery is /reload-plugins or restart, but nothing surfaces that hooks have died.
This is the mid-session sibling of the boot-time race in #63028.
What the docs confirm
The plugins reference documents the pinning: mid-session, "hook commands, monitors, MCP servers, and LSP servers keep using the previous version's path"; /reload-plugins switches them to the new path. The failure is what happens when the old path is removed while a session is still bound to it — the pinned command path no longer exists and the hook silently no-ops.
Steps to reproduce
- Install a marketplace plugin registering PreToolUse/PostToolUse/Notification hooks.
- Start several sessions; confirm hooks fire (e.g. a per-invocation debug log).
- Bump the plugin's
versionand push so Claude Code re-materializes the cache dir (removing the old versioned path). - In already-running sessions, keep working (tool calls / permission prompts).
Expected: hooks keep firing (old or new path), or the user is told a reload is needed.
Actual: the running sessions' hooks silently stop entirely — no error, nothing in the UI. The user discovers it only when a prompt/notification that should have fired doesn't. /reload-plugins recovers it, with no signal it's needed.
Concrete real-world repro (observed)
A tmux-notification plugin was bumped and re-materialized mid-day. Its debug log records every hook invocation with a session id. Afterward: ~6 sessions demonstrably alive (operators actively conversing, transcripts growing) each logged a last hook event, then went silent for 13,000–17,000 subsequent log lines while other sessions kept logging thousands. Clean cliff — no session's last-event lands in the gap — consistent with one re-materialization dropping hooks for every live session at that instant. Survivors were started after the event. Restart / /reload-plugins the only recovery.
Why in-plugin mitigation is impossible (new)
A plugin cannot detect or self-heal this, because the plugin's own detection hook is pinned to the same removed path — it dies in the same event it would need to report. We built exactly such a heartbeat (a SessionStart hook that stamps a per-session liveness file); it catches the boot-time race (#63028) but is structurally blind to this bug, because re-materialization removes its path too. Any fix or reliable detection has to come from either the provider (Claude Code itself) or a hook registered outside the plugin (in settings.json).
A verified out-of-band detection mechanism (new)
settings.json hooks are not plugin-pinned. They're read from ~/.claude/settings.json (or project .claude/settings.json), which re-materialization never touches — and per the settings docs, Claude Code watches those files and reloads hooks on change mid-session. So a settings-level hook survives the very event that kills plugin-pinned hooks, making it a viable watchdog for a plugin-level failure. The root cause is observable — a directory that existed at session start is removed — so a watchdog can watch for exactly that:
- On
SessionStart, snapshot the plugin version dirs present now. - On
PreToolUse(fires every tool call), warn via a non-blockingsystemMessageif any snapshotted dir has vanished, then refresh state so it warns once per disappearance rather than every turn.
systemMessage is a documented, non-blocking hook output field (hooks reference, "JSON Output Fields"); it surfaces to the user without stopping the turn. (Community commenter @yurukusa proposed this shape on the original issue and verified the snapshot/detect/refresh logic against simulated dir removal; it went unanswered before the stale-close.)
This is a workaround for detection, not a fix. It turns a silent data-loss-class failure (missed permission prompts / notifications) into a loud, actionable one. It cannot restore the dead hooks — only the provider can.
Requested fix (either resolves it)
- Auto-re-register hooks to the new path on re-materialization / plugin update, so a live session self-heals instead of pointing at a deleted path — or at minimum keep the old path readable until sessions bound to it end.
- A hook-health introspection surface so the silent failure is detectable in-product: per-plugin config hash, accepted/rejected hook counts with reasons, active phases, and a last-fire timestamp per event, plus a doctor-style check ("N sessions have stale/dead hook registration; run
/reload-pluginsor restart"). Even a loud warning on the affected session would be a large improvement over silent death.
Why this is not a duplicate
- #55867 ("Mid-session hook configuration reload") — closed as duplicate; covers the general no-mid-session-reload gap for settings.json + hook-script bodies, not the re-materialization-removes-pinned-installPath → silent total hook death across live sessions; produced no fix.
- #52218 — scoped to
autoUpdaterewritinginstalled_plugins.json. This is broader: ordinary marketplace re-materialization after a version bump, and the killer is the old path being removed, not a stale pointer to a still-present path. - #63028 (declared plugins inactive on first session), #10997 / #11509 (SessionStart hooks miss first run) — boot-time races. This is the mid-session variant.
- #64699 ("Hooks stop firing entirely after editing settings.local.json") — same class (a mid-session config event silently kills hooks; restart doesn't always recover), different trigger.
Related
- #73952 (this issue's prior filing, stale-closed)
- #63028 (boot-time inactive plugins) — sibling race
- #64699 (hooks die after settings.local.json edit) — same class
- #55867 (closed as dup), #52218 (autoUpdate-scoped), #56631 (reload incomplete), #10997 / #11509 (first-run SessionStart)