fix(core): switch to subshells for shell tool wrapping to fix heredocs and edge cases (#24024)

This commit is contained in:
Abhi
2026-03-27 16:34:39 -04:00
committed by GitHub
parent 320c8aba4c
commit ba71ffa736
2 changed files with 71 additions and 11 deletions

View File

@@ -277,7 +277,7 @@ describe('ShellTool', () => {
const result = await promise;
const wrappedCommand = `{ my-command & }; __code=$?; pgrep -g 0 >${tmpFile} 2>&1; exit $__code;`;
const wrappedCommand = `(\n${'my-command &'}\n); __code=$?; pgrep -g 0 >${tmpFile} 2>&1; exit $__code;`;
expect(mockShellExecutionService).toHaveBeenCalledWith(
wrappedCommand,
tempRootDir,
@@ -295,6 +295,42 @@ describe('ShellTool', () => {
expect(fs.existsSync(tmpFile)).toBe(false);
});
it('should add a space when command ends with a backslash to prevent escaping newline', async () => {
const invocation = shellTool.build({ command: 'ls\\' });
const promise = invocation.execute(mockAbortSignal);
resolveShellExecution();
await promise;
const tmpFile = path.join(os.tmpdir(), 'shell_pgrep_abcdef.tmp');
const wrappedCommand = `(\nls\\ \n); __code=$?; pgrep -g 0 >${tmpFile} 2>&1; exit $__code;`;
expect(mockShellExecutionService).toHaveBeenCalledWith(
wrappedCommand,
tempRootDir,
expect.any(Function),
expect.any(AbortSignal),
false,
expect.any(Object),
);
});
it('should handle trailing comments correctly by placing them on their own line', async () => {
const invocation = shellTool.build({ command: 'ls # comment' });
const promise = invocation.execute(mockAbortSignal);
resolveShellExecution();
await promise;
const tmpFile = path.join(os.tmpdir(), 'shell_pgrep_abcdef.tmp');
const wrappedCommand = `(\nls # comment\n); __code=$?; pgrep -g 0 >${tmpFile} 2>&1; exit $__code;`;
expect(mockShellExecutionService).toHaveBeenCalledWith(
wrappedCommand,
tempRootDir,
expect.any(Function),
expect.any(AbortSignal),
false,
expect.any(Object),
);
});
it('should use the provided absolute directory as cwd', async () => {
const subdir = path.join(tempRootDir, 'subdir');
const invocation = shellTool.build({
@@ -306,7 +342,7 @@ describe('ShellTool', () => {
await promise;
const tmpFile = path.join(os.tmpdir(), 'shell_pgrep_abcdef.tmp');
const wrappedCommand = `{ ls; }; __code=$?; pgrep -g 0 >${tmpFile} 2>&1; exit $__code;`;
const wrappedCommand = `(\n${'ls'}\n); __code=$?; pgrep -g 0 >${tmpFile} 2>&1; exit $__code;`;
expect(mockShellExecutionService).toHaveBeenCalledWith(
wrappedCommand,
subdir,
@@ -331,7 +367,7 @@ describe('ShellTool', () => {
await promise;
const tmpFile = path.join(os.tmpdir(), 'shell_pgrep_abcdef.tmp');
const wrappedCommand = `{ ls; }; __code=$?; pgrep -g 0 >${tmpFile} 2>&1; exit $__code;`;
const wrappedCommand = `(\n${'ls'}\n); __code=$?; pgrep -g 0 >${tmpFile} 2>&1; exit $__code;`;
expect(mockShellExecutionService).toHaveBeenCalledWith(
wrappedCommand,
path.join(tempRootDir, 'subdir'),