#9603: fix: initialize global hook runner on plugin registry cache hit
stale
Cluster:
Hook and Gateway Improvements
## Problem
When the plugin loader returns a cached registry (same workspace dir + same plugins config), it calls `setActivePluginRegistry(cached, cacheKey)` but skips `initializeGlobalHookRunner()`. This causes **all plugin typed hooks to stop firing** after an in-process SIGUSR1 restart.
The global hook runner singleton is not re-bound to the active registry on cache hit, so `getGlobalHookRunner()` returns a stale or null runner. Every hook call site short-circuits at `hasHooks()`.
## Impact
- All workspace and global plugins using `api.on()` typed hooks silently break after any config change that triggers SIGUSR1
- `before_tool_call`, `after_tool_call`, `before_agent_start`, `agent_end`, and all other typed hooks stop firing
- No error messages — hooks just silently don't execute
## Fix
One-line addition: call `initializeGlobalHookRunner(cached)` on cache hit, matching the cold-load path behavior.
## Reproducer
1. Create a workspace plugin that registers typed hooks via `api.on('before_tool_call', handler)`
2. Start gateway — hooks fire correctly
3. Apply a config change (e.g., update agents config) that triggers SIGUSR1 in-process restart
4. Plugin hooks stop firing silently
5. Only a full process restart (kill + respawn) recovers
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR fixes a plugin reload edge case by ensuring the global typed-hook runner singleton is re-initialized when the plugin loader returns a cached `PluginRegistry`. Previously, cache hits called `setActivePluginRegistry(cached, cacheKey)` but skipped `initializeGlobalHookRunner()`, leaving `getGlobalHookRunner()` bound to a stale/null registry after an in-process SIGUSR1 restart and causing typed hooks (e.g. `before_tool_call`, `after_tool_call`, agent lifecycle hooks) to silently stop firing.
Changes:
- `src/plugins/loader.ts`: on registry cache hit, call `initializeGlobalHookRunner(cached)` before returning.
- `package.json` / `pnpm-lock.yaml`: add `redis` dependency and update lockfile accordingly (note this is unrelated to the hook-runner fix and expands the dependency surface).
<h3>Confidence Score: 4/5</h3>
- This PR is likely safe once the unrelated dependency changes are removed or split out.
- The functional change in `src/plugins/loader.ts` is a straightforward, consistent call to `initializeGlobalHookRunner()` on cache hits and matches the cold-load path. The main concern is the unrelated addition of `redis` and associated lockfile churn, which increases review surface and risk for a fix PR.
- package.json, pnpm-lock.yaml
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#20541: fix(hooks): clear internal hooks before plugins register
by ramarnat · 2026-02-19
81.2%
#10679: fix(hooks): invoke gateway_start and gateway_stop in lifecycle
by yassinebkr · 2026-02-06
80.5%
#14746: fix(hooks): use globalThis for handler registry to survive bundler ...
by openperf · 2026-02-12
79.6%
#23019: fix(hooks): use globalThis singleton for internal hooks handlers Map
by karmafeast · 2026-02-21
79.3%
#17930: fix: evaluate tool_result_persist hooks lazily to avoid race condition
by TheArkifaneVashtorr · 2026-02-16
77.4%
#3749: fix(plugins): invoke before_compaction and after_compaction hooks d...
by taronsung · 2026-01-29
77.4%
#15611: fix(gateway): invalidate hook transform cache on config reload
by AI-Reviewer-QS · 2026-02-13
77.3%
#15571: feat: infrastructure foundation — hooks, model failover, sessions, ...
by tangcruz · 2026-02-13
76.7%
#16960: perf: skip cache-busting for bundled hooks, use mtime for workspace...
by mudrii · 2026-02-15
76.2%
#6853: fix: fire internal hooks on sessions.reset RPC (TUI/webchat /new)
by hamiltonchua · 2026-02-02
76.0%