From fe1cfd885ca21f904e73cf6b1eadea561aaa38ca Mon Sep 17 00:00:00 2001 From: kilhyeonjun Date: Fri, 10 Apr 2026 10:40:12 +0900 Subject: [PATCH] fix: preserve accumulated modifiedInput and common fields on deny/ask from exit code paths When a hook returns exit code 2 (deny) or 1 (ask), previously accumulated modifiedInput and common fields from earlier allow hooks were discarded. Now all exit paths (exit code and JSON) include accumulated state. --- .../claude-code-hooks/pre-tool-use.test.ts | 32 +++++++++++++++++++ src/hooks/claude-code-hooks/pre-tool-use.ts | 4 +++ 2 files changed, 36 insertions(+) diff --git a/src/hooks/claude-code-hooks/pre-tool-use.test.ts b/src/hooks/claude-code-hooks/pre-tool-use.test.ts index fc476884b..13770a47f 100644 --- a/src/hooks/claude-code-hooks/pre-tool-use.test.ts +++ b/src/hooks/claude-code-hooks/pre-tool-use.test.ts @@ -216,5 +216,37 @@ describe("executePreToolUseHooks", () => { expect(result.suppressOutput).toBe(true) expect(result.systemMessage).toBe("Budget warning: approaching limit") }) + + it("#when first hook allows with modifiedInput and second hook denies #then deny includes accumulated modifiedInput", async () => { + let callCount = 0 + dispatchSpy.mockImplementation(async () => { + callCount++ + if (callCount === 1) { + return { + exitCode: 0, + stdout: JSON.stringify({ + decision: "allow", + hookSpecificOutput: { + permissionDecision: "allow", + updatedInput: { file_path: "/tmp/modified.md" }, + }, + }), + stderr: "", + } + } + return { exitCode: 2, stdout: "", stderr: "BUDGET EXCEEDED" } + }) + + const config = createConfig([ + { matcher: "*", hooks: [{ type: "command", command: "node modifier.mjs" }] }, + { matcher: "Edit|Write", hooks: [{ type: "command", command: "bash budget-guard.sh" }] }, + ]) + + const result = await executePreToolUseHooks(createContext(), config) + + expect(callCount).toBe(2) + expect(result.decision).toBe("deny") + expect(result.modifiedInput).toEqual({ file_path: "/tmp/modified.md" }) + }) }) }) diff --git a/src/hooks/claude-code-hooks/pre-tool-use.ts b/src/hooks/claude-code-hooks/pre-tool-use.ts index e7ac9b6e0..a6d03182a 100644 --- a/src/hooks/claude-code-hooks/pre-tool-use.ts +++ b/src/hooks/claude-code-hooks/pre-tool-use.ts @@ -100,10 +100,12 @@ export async function executePreToolUseHooks( return { decision: "deny", reason: result.stderr || result.stdout || "Hook blocked the operation", + modifiedInput: accumulatedModifiedInput, elapsedMs: Date.now() - startTime, hookName: firstHookName, toolName: transformedToolName, inputLines, + ...accumulatedCommonFields, } } @@ -111,10 +113,12 @@ export async function executePreToolUseHooks( return { decision: "ask", reason: result.stderr || result.stdout, + modifiedInput: accumulatedModifiedInput, elapsedMs: Date.now() - startTime, hookName: firstHookName, toolName: transformedToolName, inputLines, + ...accumulatedCommonFields, } }