[BUG] Marketplace plugin re-materialization mid-session silently kills pinned hooks in every running session — and the failure is undetectable from inside the plugin

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 27, 2026

[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

  1. Install a marketplace plugin registering PreToolUse/PostToolUse/Notification hooks.
  2. Start several sessions; confirm hooks fire (e.g. a per-invocation debug log).
  3. Bump the plugin's version and push so Claude Code re-materializes the cache dir (removing the old versioned path).
  4. 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-blocking systemMessage if 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)

  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 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-plugins or 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 autoUpdate rewriting installed_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)

View original on GitHub ↗