[BUG] Marketplace plugin re-materialization mid-session cleans the pinned hook installPath, silently killing hooks in every running session

Open 💬 1 comment Opened Jul 3, 2026 by amilligan

[BUG] Marketplace plugin re-materialization mid-session cleans the pinned hook installPath, silently killing hooks in every running session

Summary

When a marketplace plugin is updated mid-session (new version bump + marketplace re-materialization), a running session's hooks are pinned to the previous version's install path. The re-materialization removes that old path, so the pinned hook commands become unreachable and silently stop firing — no error, no warning, and the session otherwise runs normally. Every session that was live at the moment of re-materialization loses that plugin's hooks at once. The only recovery is /reload-plugins (if the user happens to know to run it) or a full restart; nothing surfaces that hooks have died.

This is the mid-session sibling of the boot-time race in #63028, and it is distinct from the already-closed issues (see "Why this is not a duplicate" below).

Environment

  • Claude Code CLI, marketplace-installed plugin (git-backed marketplace)
  • Plugin declares hooks in hooks/hooks.json (Notification, PreToolUse, PostToolUse, PermissionDenied)
  • Many long-running sessions across a tmux fleet

What the docs say (this is the pinned-path mechanism)

The Plugins reference documents the pinning directly:

"When a plugin updates mid-session, hook commands, monitors, MCP servers, and LSP servers keep using the previous version's path. Run /reload-plugins to switch hooks, MCP servers, and LSP servers to the new path; monitors require a session restart."

So hooks are snapshotted at session start and bound to that version's install path. The failure is what happens when the old path is removed by re-materialization while a session is still bound to it: the pinned command path no longer exists, and the hook silently no-ops.

Steps to reproduce

  1. Install a marketplace plugin that registers PreToolUse/PostToolUse/Notification hooks.
  2. Start several Claude Code sessions; confirm hooks fire (e.g. the plugin writes a per-invocation debug log).
  3. Bump the plugin's version in plugin.json and push to the marketplace so Claude Code re-materializes the plugin cache dir (replacing/removing the old versioned path).
  4. In the already-running sessions, keep working (keep hitting tool calls / permission prompts).

Expected: hooks continue firing (from either the old or the new path), OR the user is told the plugin was updated and hooks need reloading.

Actual: the running sessions' hooks silently stop firing entirely. No error is printed, nothing in the UI changes, and the sessions keep working — the user only discovers it when a permission prompt or notification that should have fired doesn't. /reload-plugins recovers it, but there is no signal that it's needed.

Concrete real-world repro (observed)

A plugin (claude-tmux-attention, a tmux notification plugin) was bumped from 0.2.4 and re-materialized mid-day. Its debug log records every hook invocation with a session id. Sweeping the log afterward: ~6 sessions that were demonstrably alive (their operators were actively conversing with them, transcripts growing) each logged a last hook event and then went totally silent for 13,000–17,000 subsequent log lines while other sessions kept logging thousands. There is a clean cliff — no session's last-event lands in the gap — consistent with a single re-materialization event dropping hooks for every session live at that instant. Survivors are sessions started after the event. Restart (or /reload-plugins) is the only recovery.

Why this is not a duplicate of existing issues

  • #55867 ("Mid-session hook configuration reload") was closed as duplicate. It covers the general "no mid-session reload" gap for settings.json + hook-script bodies; it does not track the specific re-materialization-removes-pinned-installPath → silent total hook death across live sessions, and it produced no fix.
  • #52218 ("Plugin autoUpdate doesn't update installed_plugins.json, hooks pinned to stale installPath") is scoped to autoUpdate rewriting installed_plugins.json. This report is broader: it happens on ordinary marketplace re-materialization after a version bump, and the killer is the old path being cleaned away, not just a stale pointer to a still-present path.
  • #63028 (declared plugins inactive on first session) and #10997 / #11509 (SessionStart hooks miss first run) are boot-time races. This is the mid-session variant.
  • #64699 ("Hooks stop firing entirely after editing settings.local.json") is open and the same class (a mid-session config event silently kills hooks; restart doesn't always recover) — related, different trigger.

Requested fix (either would resolve it)

  1. 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.
  2. A hook-health introspection surface so the silent failure becomes detectable. The #64699 commenter's proposal is a good shape: after any reload/re-materialization, expose per-plugin config hash, accepted/rejected hook count with reasons, active phases, and a last-fire timestamp per event — plus a claude-doctor-style check ("N sessions have stale/dead hook registration; run /reload-plugins or restart"). Even a loud warning on the affected session would be a large improvement over the current silent death.

A detection surface alone (option 2) would be enough to turn this from a silent data-loss-class failure (missed permission prompts / notifications) into a loud, actionable one.

Related

  • #63028 (boot-time inactive plugins) — sibling race, open
  • #64699 (hooks die after settings.local.json edit) — same class, open
  • #55867 (closed as dup), #52218 (autoUpdate-scoped), #56631 (reload incomplete), #10997 / #11509 (first-run SessionStart)

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗