From ba571c1e72f355877e5ef535452efa44f649e001 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 e5b7fd40bbc8f7909b03f7474c69b5a7ec3698df 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 8500abeb39aaec4cda544ae4417fe4fd8236bd6f 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 }