From 3c7e0451f2332a7f37b58e5daac04cb89e9a7339 Mon Sep 17 00:00:00 2001 From: Strocs Date: Fri, 13 Feb 2026 13:21:58 -0300 Subject: [PATCH 1/3] fix(non-interactive-env): prevent environment variable duplication on repeated executions The non-interactive-env hook was prepending environment variables without checking if the prefix was already applied to the command, causing duplication when multiple git commands were executed in sequence. This fix adds an idempotent check: if the command already starts with the env prefix, the hook returns early without modification. This maintains the non-interactive behavior while ensuring the operation is idempotent across multiple tool executions. --- src/hooks/non-interactive-env/non-interactive-env-hook.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/hooks/non-interactive-env/non-interactive-env-hook.ts b/src/hooks/non-interactive-env/non-interactive-env-hook.ts index 90686f64d..46ab2e09e 100644 --- a/src/hooks/non-interactive-env/non-interactive-env-hook.ts +++ b/src/hooks/non-interactive-env/non-interactive-env-hook.ts @@ -55,6 +55,13 @@ export function createNonInteractiveEnvHook(_ctx: PluginInput) { // The bash tool always runs in a Unix-like shell (bash/sh), even on Windows // (via Git Bash, WSL, etc.), so always use unix export syntax. const envPrefix = buildEnvPrefix(NON_INTERACTIVE_ENV, "unix") + + // Check if the command already starts with the prefix to avoid stacking. + // This maintain the non-interactive behaivor but make the operation idempotent. + if (command.trim().startsWith(envPrefix.trim())) { + return + } + output.args.command = `${envPrefix} ${command}` log(`[${HOOK_NAME}] Prepended non-interactive env vars to git command`, { From 2af002e71f72f76c426c1928776418a18f11c5de Mon Sep 17 00:00:00 2001 From: Strocs Date: Fri, 13 Feb 2026 21:51:38 -0300 Subject: [PATCH 2/3] test(non-interactive-env): add idempotency test for env prefix injection --- src/hooks/non-interactive-env/index.test.ts | 28 +++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/hooks/non-interactive-env/index.test.ts b/src/hooks/non-interactive-env/index.test.ts index 7eed35294..8e4bed295 100644 --- a/src/hooks/non-interactive-env/index.test.ts +++ b/src/hooks/non-interactive-env/index.test.ts @@ -111,6 +111,34 @@ describe("non-interactive-env hook", () => { expect(output.args.command).toBeUndefined() }) + + test("#given git command already has prefix #when hook executes again #then does not duplicate prefix", async () => { + const hook = createNonInteractiveEnvHook(mockCtx) + + // First call: transforms the command + const output1: { args: Record; message?: string } = { + args: { command: "git commit -m 'test'" }, + } + await hook["tool.execute.before"]( + { tool: "bash", sessionID: "test", callID: "1" }, + output1 + ) + + const firstResult = output1.args.command as string + expect(firstResult).toStartWith("export ") + + // Second call: takes the already-prefixed command + const output2: { args: Record; message?: string } = { + args: { command: firstResult }, + } + await hook["tool.execute.before"]( + { tool: "bash", sessionID: "test", callID: "2" }, + output2 + ) + + // Should be exactly the same (no double prefix) + expect(output2.args.command).toBe(firstResult) + }) }) describe("shell escaping", () => { From a864b54ba30c00d349b2f083d972d37826e72936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Andr=C3=A9s=20Molina?= Date: Fri, 13 Feb 2026 21:36:32 -0300 Subject: [PATCH 3/3] docs(non-interactive-env): fix typos in idempotency comment --- src/hooks/non-interactive-env/non-interactive-env-hook.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/non-interactive-env/non-interactive-env-hook.ts b/src/hooks/non-interactive-env/non-interactive-env-hook.ts index 46ab2e09e..758a1eb78 100644 --- a/src/hooks/non-interactive-env/non-interactive-env-hook.ts +++ b/src/hooks/non-interactive-env/non-interactive-env-hook.ts @@ -57,7 +57,7 @@ export function createNonInteractiveEnvHook(_ctx: PluginInput) { const envPrefix = buildEnvPrefix(NON_INTERACTIVE_ENV, "unix") // Check if the command already starts with the prefix to avoid stacking. - // This maintain the non-interactive behaivor but make the operation idempotent. + // This maintains the non-interactive behavior and makes the operation idempotent. if (command.trim().startsWith(envPrefix.trim())) { return }