From 0b332b41d791efdd7c80d2cbe7b6d4dfd8cf0266 Mon Sep 17 00:00:00 2001 From: cocosheng-g Date: Mon, 2 Mar 2026 17:33:18 -0500 Subject: [PATCH] fix(core): safely append GIT_CONFIG overrides without breaking user config --- .../services/shellExecutionService.test.ts | 24 +++++++++++++++---- .../src/services/shellExecutionService.ts | 14 +++++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/core/src/services/shellExecutionService.test.ts b/packages/core/src/services/shellExecutionService.test.ts index 3a9e396a25..4161fb8999 100644 --- a/packages/core/src/services/shellExecutionService.test.ts +++ b/packages/core/src/services/shellExecutionService.test.ts @@ -1704,8 +1704,14 @@ describe('ShellExecutionService environment variables', () => { await new Promise(process.nextTick); }); - it('should include headless git and gh environment variables in non-interactive mode', async () => { + it('should include headless git and gh environment variables in non-interactive mode and append git config safely', async () => { vi.resetModules(); + vi.stubEnv('GIT_CONFIG_COUNT', '2'); + vi.stubEnv('GIT_CONFIG_KEY_0', 'core.editor'); + vi.stubEnv('GIT_CONFIG_VALUE_0', 'vim'); + vi.stubEnv('GIT_CONFIG_KEY_1', 'pull.rebase'); + vi.stubEnv('GIT_CONFIG_VALUE_1', 'true'); + const { ShellExecutionService } = await import( './shellExecutionService.js' ); @@ -1729,13 +1735,23 @@ describe('ShellExecutionService environment variables', () => { expect(cpEnv).toHaveProperty('GCM_INTERACTIVE', 'never'); expect(cpEnv).toHaveProperty('DISPLAY', ''); expect(cpEnv).toHaveProperty('DBUS_SESSION_BUS_ADDRESS', ''); - expect(cpEnv).toHaveProperty('GIT_CONFIG_COUNT', '1'); - expect(cpEnv).toHaveProperty('GIT_CONFIG_KEY_0', 'credential.helper'); - expect(cpEnv).toHaveProperty('GIT_CONFIG_VALUE_0', ''); + + // Existing values should be preserved + expect(cpEnv).toHaveProperty('GIT_CONFIG_KEY_0', 'core.editor'); + expect(cpEnv).toHaveProperty('GIT_CONFIG_VALUE_0', 'vim'); + expect(cpEnv).toHaveProperty('GIT_CONFIG_KEY_1', 'pull.rebase'); + expect(cpEnv).toHaveProperty('GIT_CONFIG_VALUE_1', 'true'); + + // The new credential.helper override should be appended at index 2 + expect(cpEnv).toHaveProperty('GIT_CONFIG_COUNT', '3'); + expect(cpEnv).toHaveProperty('GIT_CONFIG_KEY_2', 'credential.helper'); + expect(cpEnv).toHaveProperty('GIT_CONFIG_VALUE_2', ''); // Ensure child_process exits mockChildProcess.emit('exit', 0, null); mockChildProcess.emit('close', 0, null); await new Promise(process.nextTick); + + vi.unstubAllEnvs(); }); }); diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index e18e129991..3c64356944 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -305,6 +305,12 @@ export class ShellExecutionService { const guardedCommand = ensurePromptvarsDisabled(commandToExecute, shell); const spawnArgs = [...argsPrefix, guardedCommand]; + const sanitizedEnv = sanitizeEnvironment(process.env, sanitizationConfig); + const gitConfigCount = parseInt( + sanitizedEnv['GIT_CONFIG_COUNT'] || '0', + 10, + ); + const child = cpSpawn(executable, spawnArgs, { cwd, stdio: ['ignore', 'pipe', 'pipe'], @@ -312,7 +318,7 @@ export class ShellExecutionService { shell: false, detached: !isWindows, env: { - ...sanitizeEnvironment(process.env, sanitizationConfig), + ...sanitizedEnv, [GEMINI_CLI_IDENTIFICATION_ENV_VAR]: GEMINI_CLI_IDENTIFICATION_ENV_VAR_VALUE, TERM: 'xterm-256color', @@ -325,9 +331,9 @@ export class ShellExecutionService { GCM_INTERACTIVE: 'never', DISPLAY: '', DBUS_SESSION_BUS_ADDRESS: '', - GIT_CONFIG_COUNT: '1', - GIT_CONFIG_KEY_0: 'credential.helper', - GIT_CONFIG_VALUE_0: '', + GIT_CONFIG_COUNT: (gitConfigCount + 1).toString(), + [`GIT_CONFIG_KEY_${gitConfigCount}`]: 'credential.helper', + [`GIT_CONFIG_VALUE_${gitConfigCount}`]: '', PAGER: 'cat', GIT_PAGER: 'cat', },