From 54ce282ccd1327aa939d4c85b873d3f05516c083 Mon Sep 17 00:00:00 2001 From: lewis617 Date: Thu, 28 May 2026 19:03:59 +0800 Subject: [PATCH] fix: stop hook executing multiple times due to duplicate config loading loadConfigurationFromWaveConfig() was called multiple times during init (initializationService + liveConfigManager reload) and used mergeHooksConfiguration() which appends, causing the same hook from settings.json to be registered 2-3+ times. Fix: track the three hook sources (programmatic, plugin, wave config) separately and replace (not append) wave config hooks on each reload. A new rebuildConfiguration() merges all sources into the final config. --- .../agent-sdk/src/managers/hookManager.ts | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/packages/agent-sdk/src/managers/hookManager.ts b/packages/agent-sdk/src/managers/hookManager.ts index 1427d2e8..71f7002e 100644 --- a/packages/agent-sdk/src/managers/hookManager.ts +++ b/packages/agent-sdk/src/managers/hookManager.ts @@ -31,6 +31,9 @@ import { logger } from "../utils/globalLogger.js"; export class HookManager { private configuration: PartialHookConfiguration | undefined; + private programmaticHooks: PartialHookConfiguration = {}; + private pluginHooks: PartialHookConfiguration = {}; + private waveConfigHooks: PartialHookConfiguration = {}; private readonly matcher: HookMatcher; private readonly workdir: string; @@ -47,14 +50,16 @@ export class HookManager { * Load hook configuration from programmatic source (AgentOptions.hooks) */ loadConfiguration(hooks?: PartialHookConfiguration): void { - const merged: PartialHookConfiguration = {}; + this.programmaticHooks = {}; if (hooks) { - this.mergeHooksConfiguration(merged, hooks); + this.mergeHooksConfiguration(this.programmaticHooks, hooks); } // Validate merged configuration - const validation = this.validatePartialConfiguration(merged); + const validation = this.validatePartialConfiguration( + this.programmaticHooks, + ); if (!validation.valid) { throw new HookConfigurationError( "merged configuration", @@ -62,7 +67,7 @@ export class HookManager { ); } - this.configuration = merged; + this.rebuildConfiguration(); } /** @@ -71,8 +76,9 @@ export class HookManager { */ loadConfigurationFromWaveConfig(waveConfig: WaveConfiguration | null): void { try { - // Merge Wave configuration hooks with existing plugin hooks - // (plugin hooks were registered earlier via registerPluginHooks) + // Replace (not append) wave config hooks to avoid duplicates on reload + this.waveConfigHooks = {}; + if (waveConfig?.hooks) { const validation = this.validatePartialConfiguration(waveConfig.hooks); if (!validation.valid) { @@ -81,11 +87,10 @@ export class HookManager { validation.errors, ); } - if (!this.configuration) { - this.configuration = {}; - } - this.mergeHooksConfiguration(this.configuration, waveConfig.hooks); + this.waveConfigHooks = { ...waveConfig.hooks }; } + + this.rebuildConfiguration(); } catch (error) { // Re-throw configuration errors, but handle other errors gracefully if (error instanceof HookConfigurationError) { @@ -98,6 +103,19 @@ export class HookManager { } } + /** + * Rebuild the full configuration from all sources: + * programmatic (AgentOptions.hooks) + plugin + wave config (settings.json) + * Order determines precedence on conflict (later sources append). + */ + private rebuildConfiguration(): void { + const rebuilt: PartialHookConfiguration = {}; + this.mergeHooksConfiguration(rebuilt, this.programmaticHooks); + this.mergeHooksConfiguration(rebuilt, this.pluginHooks); + this.mergeHooksConfiguration(rebuilt, this.waveConfigHooks); + this.configuration = Object.keys(rebuilt).length > 0 ? rebuilt : undefined; + } + /** * Execute hooks for a specific event */ @@ -854,10 +872,6 @@ export class HookManager { pluginRoot: string, hooks: PartialHookConfiguration, ): void { - if (!this.configuration) { - this.configuration = {}; - } - // Stamp pluginRoot on each hook command const stampedHooks: PartialHookConfiguration = {}; for (const [event, configs] of Object.entries(hooks)) { @@ -868,7 +882,8 @@ export class HookManager { })); } - this.mergeHooksConfiguration(this.configuration, stampedHooks); + this.mergeHooksConfiguration(this.pluginHooks, stampedHooks); + this.rebuildConfiguration(); } /**