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", () => { 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..758a1eb78 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 maintains the non-interactive behavior and makes 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`, {