← Back to PRs

#14746: fix(hooks): use globalThis for handler registry to survive bundler code-splitting

by openperf open 2026-02-12 15:38 View on GitHub →
stale size: S
## 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