google-gemini / google-gemini/gemini-cli

File descriptor leak in ShellExecutionService.background() when command exits before 200ms delay

Open
#22,814 3 comments 0 reactions 0 assignees View on GitHub
🔒 maintainer only area/core effort/medium kind/bug priority/p2 status/bot-triaged type/bug
Dominant language
TypeScript
Stars
107k
Forks
14.6k
Avg merge
2d 3h
Merged PRs (30d)
45

Description

### What happened?

In packages/core/src/services/shellExecutionService.ts, the static background(pid) method (line 1197) unconditionally creates an fs.WriteStream for background logging and adds the PID to backgroundLogPids/backgroundLogStreams BEFORE calling ExecutionLifecycleService.background(pid) at line 1226.

When a command marked is_background: true exits before the 200ms BACKGROUND_DELAY_MS timer fires, the following sequence occurs:

The process exit handler fires, calling cleanupLogStream(pid) at line 625 (child_process) or line 1059 (PTY). This cleans up any existing log stream for the PID.
ExecutionLifecycleService.settleExecution() runs, deleting the active resolver and active execution entry.
200ms later, ShellExecutionService.background(pid) fires.
It creates a NEW fs.WriteStream (line 1206) and registers it in backgroundLogStreams (line 1210).
It adds the PID to backgroundLogPids (line 1224).
It calls ExecutionLifecycleService.background(pid) (line 1226), which returns early at line 412 because the resolver was already deleted by settleExecution.
The newly created WriteStream is now permanently leaked because:

cleanupLogStream already ran during the process exit event.
Nobody will call cleanupLogStream again for this PID.
The stream remains open in backgroundLogStreams indefinitely.
Root Cause: ShellExecutionService.background() has no mechanism to know whether ExecutionLifecycleService.background() succeeded or failed. The log stream setup and the lifecycle state change are not atomic. ExecutionLifecycleService.background() returns void, providing no feedback.

### What did you expect to happen?

ShellExecutionService.background() should only create the log stream and register the PID if the lifecycle transition actually succeeded. ExecutionLifecycleService.background() should return a boolean indicating whether backgrounding was successful, and the log stream creation should be gated on that return value.

### Client information

CLI Version: 0.35.0-nightly.20260313.bb060d7a9 Node Version: v20.19.0 OS: Windows_NT (Windows 11) Architecture: x64

### Login information

Signed in with Google Account via OAuth flow Email:
kumaradithyabathula@gmail.com

### Anything else we need to know?

I have a validated fix ready. It modifies two files:

ExecutionLifecycleService.background() return type changed from void to boolean (returns false on early exit, true on success).
ShellExecutionService.background() gates all log stream creation and PID registration on the boolean return value.
Unit tests have been written for both the lifecycle service (verifying boolean returns) and the shell execution service integration. The fix is backward-compatible. Let me know if you would like me to push up a PR.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.