#14746: fix(hooks): use globalThis for handler registry to survive bundler code-splitting
stale
size: S
Cluster:
GlobalThis Integration Fixes
## Summary
Fixes #14270
Internal hooks (e.g., `session-memory`, `command-logger`) never fire when commands like `/new` are sent via Telegram/Discord. The root cause is that `tsdown` (Rollup) duplicates `src/hooks/internal-hooks.ts` into multiple output chunks, each with its own isolated `handlers = new Map()`. Hooks are **registered** in one chunk's Map but **triggered** against a different chunk's empty Map.
## Changes
- **`src/hooks/internal-hooks.ts`**: Replace the module-level `handlers` Map with a shared instance stored on `globalThis` under a stable key (`__openclaw_internal_hook_handlers__`). This ensures all bundler-generated copies of the module operate on the same registry.
- **`src/hooks/internal-hooks.test.ts`**: Add `globalThis registry resilience` test suite to verify the handler Map is stored on `globalThis` and that `clearInternalHooks()` correctly clears the global registry.
## Why `globalThis` instead of fixing the bundler config
Configuring `manualChunks` in tsdown/Rollup would also work, but it is fragile — any future entry point or dependency change could re-introduce the duplication silently. The `globalThis` approach is a one-line defense at the source level that is immune to bundler configuration drift.
## Testing
- `pnpm check` passes (format, type-check, lint)
- `pnpm vitest run src/hooks/internal-hooks.test.ts` passes, including the new `globalThis registry resilience` tests
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR fixes internal hook handlers not firing under tsdown/Rollup code-splitting by moving the internal hook handler registry from a module-level `Map` to a shared `Map` stored on `globalThis` under a stable key (`__openclaw_internal_hook_handlers__`). This ensures that if the bundler duplicates `src/hooks/internal-hooks.ts` across multiple output chunks, all copies still register/trigger against the same handler registry.
Tests were updated to assert the registry is actually stored on `globalThis` and that `clearInternalHooks()` clears the shared registry, aligning the behavior with other hook loader tests that call `clearInternalHooks()` for isolation.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk.
- Changes are tightly scoped to the internal hook registry initialization and are covered by targeted tests asserting the `globalThis` registry behavior. Repository-wide search did not find other uses of the global key, reducing collision risk.
- No files require special attention
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#23019: fix(hooks): use globalThis singleton for internal hooks handlers Map
by karmafeast · 2026-02-21
87.8%
#11817: fix(build): compile bundled hook handlers into dist
by AnonO6 · 2026-02-08
85.3%
#9914: fix(hooks): resolve bundled hook dist paths and packaging checks
by zimmra · 2026-02-05
81.6%
#11339: fix: resolve bundled hooks path on npm global install
by matthewpoe · 2026-02-07
80.2%
#3392: fix(hooks): remove debug console.log statements from session-memory...
by WinJayX · 2026-01-28
79.9%
#9603: fix: initialize global hook runner on plugin registry cache hit
by kevins88288 · 2026-02-05
79.6%
#9947: fix: preserve agent-events as standalone entry point for hook consu...
by therealkaiharper-wq · 2026-02-05
78.7%
#16915: fix: await compaction hooks with timeout to prevent cross-session d...
by maximalmargin · 2026-02-15
78.1%
#20471: fix: share relay auth state across bundler chunks via globalThis
by MisterGuy420 · 2026-02-19
77.7%
#20541: fix(hooks): clear internal hooks before plugins register
by ramarnat · 2026-02-19
77.4%