From 47ff524552b3cdf6607720d1293ec0aab35bc74d Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 17 Feb 2026 17:53:00 +0000 Subject: [PATCH] fix: move PreToolUse hooks after user approval and handle array content in PostToolUse - Moved PreToolUse hook execution into askApproval callback so hooks only fire after the user approves the tool, avoiding wasted API calls on rejected tools - Fixed PostToolUse hook to extract text from array-format ToolResultBlockParam.content instead of silently returning empty string --- .../presentAssistantMessage.ts | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 06b91eec28..0907b994a6 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -527,6 +527,28 @@ export async function presentAssistantMessage(cline: Task) { approvalFeedback = { text, images } } + // === PreToolUse Hook (fires after user approves to avoid wasting API calls on rejected tools) === + try { + const hooksManager = cline.providerRef.deref()?.getHooksManager() + if (hooksManager?.hasHooksForEvent("PreToolUse")) { + const matchingHooks = hooksManager.getMatchingHooks("PreToolUse", block.name) + if (matchingHooks.length > 0) { + const hookContext: HookContext = { + event: "PreToolUse", + toolName: block.name, + toolInput: block.nativeArgs || block.params, + } + const hookResults = await executeHooks(matchingHooks, hookContext, cline.apiConfiguration) + const hookOutput = formatHookResults(hookResults) + if (hookOutput) { + await cline.say("hook_output", `PreToolUse hook for ${block.name}:\n${hookOutput}`) + } + } + } + } catch (hookError) { + console.warn(`[presentAssistantMessage] PreToolUse hook error:`, hookError) + } + return true } @@ -677,30 +699,6 @@ export async function presentAssistantMessage(cline: Task) { } } - // === PreToolUse Hook === - if (!block.partial) { - try { - const hooksManager = cline.providerRef.deref()?.getHooksManager() - if (hooksManager?.hasHooksForEvent("PreToolUse")) { - const matchingHooks = hooksManager.getMatchingHooks("PreToolUse", block.name) - if (matchingHooks.length > 0) { - const hookContext: HookContext = { - event: "PreToolUse", - toolName: block.name, - toolInput: block.nativeArgs || block.params, - } - const hookResults = await executeHooks(matchingHooks, hookContext, cline.apiConfiguration) - const hookOutput = formatHookResults(hookResults) - if (hookOutput) { - await cline.say("hook_output", `PreToolUse hook for ${block.name}:\n${hookOutput}`) - } - } - } - } catch (hookError) { - console.warn(`[presentAssistantMessage] PreToolUse hook error:`, hookError) - } - } - switch (block.name) { case "write_to_file": await checkpointSaveAndMark(cline) @@ -963,11 +961,10 @@ export async function presentAssistantMessage(cline: Task) { : Array.isArray(lastResult?.content) ? lastResult.content .filter( - (b): b is import("@anthropic-ai/sdk").Anthropic.TextBlockParam => - b.type === "text", + (b): b is Anthropic.TextBlockParam => b.type === "text", ) .map((b) => b.text) - .join("\n") + .join("\n") || "" : "" const hookContext: HookContext = { event: "PostToolUse",