From b0cdbf862ffaa0e74ef39e7d901d4e526eb96afc Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 14 Feb 2026 19:44:12 +0000 Subject: [PATCH] fix: remove duplicate SubagentStart hook execution in run() (#3747) runStartHooks() and run() both called executeSubagentStartHook() for subagent requests, causing SubagentStart hooks to fire twice per subagent invocation. The caller (defaultIntentRequestHandler) always calls runStartHooks() before run(), so the call in run() was redundant. Remove the duplicate call from run() since runStartHooks() is the correct place for start hook execution. --- .../extension/intents/node/toolCallingLoop.ts | 12 -------- .../test/node/toolCallingLoopHooks.spec.ts | 30 +++++++++++++++++++ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/extensions/copilot/src/extension/intents/node/toolCallingLoop.ts b/extensions/copilot/src/extension/intents/node/toolCallingLoop.ts index 704a19c3f1c..95fe25d8896 100644 --- a/extensions/copilot/src/extension/intents/node/toolCallingLoop.ts +++ b/extensions/copilot/src/extension/intents/node/toolCallingLoop.ts @@ -558,18 +558,6 @@ export abstract class ToolCallingLoop= this.options.toolCallLimit) { lastResult = this.hitToolCallLimit(outputStream, lastResult); diff --git a/extensions/copilot/src/extension/intents/test/node/toolCallingLoopHooks.spec.ts b/extensions/copilot/src/extension/intents/test/node/toolCallingLoopHooks.spec.ts index 30dae2b8a02..0b255b8d92f 100644 --- a/extensions/copilot/src/extension/intents/test/node/toolCallingLoopHooks.spec.ts +++ b/extensions/copilot/src/extension/intents/test/node/toolCallingLoopHooks.spec.ts @@ -614,6 +614,36 @@ describe('ToolCallingLoop SubagentStart hook', () => { const input = subagentStartCalls[0].input as SubagentStartHookInput; expect(input.agent_type).toBe('default'); }); + + it('should execute SubagentStart hook only once when runStartHooks and run are both called', async () => { + const conversation = createTestConversation(1); + const request = createMockChatRequest({ + subAgentInvocationId: 'subagent-dedup', + subAgentName: 'DedupAgent', + } as Partial); + + const loop = instantiationService.createInstance( + TestToolCallingLoop, + { + conversation, + toolCallLimit: 10, + request, + } + ); + disposables.add(loop); + + // First call: runStartHooks should execute SubagentStart once + await loop.testRunStartHooks(tokenSource.token); + + // Second call: run() should NOT execute SubagentStart again + // run() will throw because fetch() is not implemented, but SubagentStart + // happens before fetch, so we need to verify it wasn't called again + await expect(loop.run(undefined, tokenSource.token)).rejects.toThrow(); + + // SubagentStart should have been called exactly once (from runStartHooks only) + const subagentStartCalls = mockChatHookService.getCallsForHook('SubagentStart'); + expect(subagentStartCalls).toHaveLength(1); + }); }); describe('SubagentStart hook result collection', () => {