Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/core/src/tools/shell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ const mockHomedir = vi.hoisted(() => vi.fn());

const mockShellExecutionService = vi.hoisted(() => vi.fn());
const mockShellBackground = vi.hoisted(() => vi.fn());
const mockShellOnExit = vi.hoisted(() => vi.fn());

vi.mock('../services/shellExecutionService.js', () => ({
ShellExecutionService: {
execute: mockShellExecutionService,
background: mockShellBackground,
onExit: mockShellOnExit,
},
}));

Expand Down
19 changes: 19 additions & 0 deletions packages/core/src/tools/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,25 @@ export class ShellToolInvocation extends BaseToolInvocation<
// If the model requested to run in the background, do so after a short delay.
let completed = false;
if (this.params.is_background) {
if (tempFilePath || tempDir) {
ShellExecutionService.onExit(pid, () => {
if (tempFilePath) {
try {
fs.unlinkSync(tempFilePath);
} catch {
// Ignore
}
}
if (tempDir) {
try {
fs.rmSync(tempDir, { recursive: true, force: true });
} catch {
// Ignore
}
}
});
}
Comment on lines +698 to +715

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There is a potential resource leak if the background process fails to spawn or if an error is thrown before the onExit handler is registered. In those cases, onExit is never registered, and the finally block (lines 1088-1103) will skip cleanup because this.params.is_background is true.

To fix this completely, we should clear tempFilePath and tempDir once the cleanup is successfully deferred to onExit, and update the finally block to clean up any remaining non-empty paths:

// In the finally block (around line 1088):
if (!this.params.is_background || tempFilePath || tempDir) {
  if (tempFilePath) {
    try {
      await fsPromises.unlink(tempFilePath);
    } catch {
      // Ignore errors during unlink
    }
  }
  if (tempDir) {
    try {
      await fsPromises.rm(tempDir, { recursive: true, force: true });
    } catch {
      // Ignore errors during rm
    }
  }
}
          if (tempFilePath || tempDir) {
            const fileToClean = tempFilePath;
            const dirToClean = tempDir;
            ShellExecutionService.onExit(pid, () => {
              if (fileToClean) {
                try {
                  fs.unlinkSync(fileToClean);
                } catch {
                  // Ignore
                }
              }
              if (dirToClean) {
                try {
                  fs.rmSync(dirToClean, { recursive: true, force: true });
                } catch {
                  // Ignore
                }
              }
            });
            tempFilePath = '';
            tempDir = '';
          }


resultPromise
.then(() => {
completed = true;
Expand Down
Loading