Skip to content

fix(sandbox): normalize DEBUG environment variable truthiness (#28885) - #29005

Closed
Eswar809 wants to merge 1 commit into
google-gemini:mainfrom
Eswar809:fix/sandbox-debug-mode-parsing
Closed

Eswar809 wants to merge 1 commit into
google-gemini:mainfrom
Eswar809:fix/sandbox-debug-mode-parsing

Conversation

@Eswar809

Copy link
Copy Markdown
Contributor

Summary

Normalizes the interpretation of the DEBUG environment variable across sandbox utilities. This prevents string values like "false" and "0" from inadvertently enabling debug features such as port publishing, pull logging, and --inspect-brk execution pauses in Docker, Podman, and macOS Seatbelt sandboxes.

Problem

Previously, packages/cli/src/utils/sandbox.ts evaluated process.env.DEBUG using standard JavaScript string truthiness. Because non-empty strings like "false" and "0" evaluate to true in JavaScript, this caused several unintended behaviors:

  • Docker/Podman Sandboxes: Unnecessarily published the debug port (--publish 9229:9229).
  • macOS Seatbelt Sandboxes: Injected --inspect-brk into NODE_OPTIONS, unexpectedly pausing CLI execution.
  • General Overhead: Enabled debug console patching and image-pull logging even when debugging was explicitly disabled by the user.

Fix

Introduced a centralized isDebugModeEnabled() helper in sandboxUtils.ts.

Instead of relying on string truthiness, this helper strictly checks for explicitly enabled values ("true", "1", or an active CLI debug mode flag). This aligns the sandbox logic with existing behaviors in the entrypoint and config.ts.

Related Issues

Fixes #28885

Validation

Run the unit tests for the sandbox utilities:

npm test -w @google/gemini-cli -- src/utils/sandboxUtils.test.ts src/utils/sandbox.test.ts

Verify that all parameterized test cases pass, specifically covering:

  • DEBUG=false
  • DEBUG=0
  • DEBUG=true
  • DEBUG=1
  • Unset/empty states

Pre-Merge Checklist

  • Added or updated tests where needed
  • Validated on required platforms/methods
  • Windows
  • Verified relevant npm scripts pass (npm run ...)

@Eswar809
Eswar809 requested a review from a team as a code owner August 24, 2026 07:42
@github-actions github-actions Bot added the size/m A medium sized PR label Aug 24, 2026
@github-actions

github-actions Bot commented Aug 24, 2026 •

Copy link
Copy Markdown

📊 PR Size: size/M

  • Lines changed: 162
  • Additions: +155
  • Deletions: -7
  • Files changed: 4

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the sandbox utility logic to fix an issue where the DEBUG environment variable was being interpreted too loosely. By replacing implicit truthiness checks with a centralized, strict validation helper, the CLI now correctly handles falsy values like 'false' and '0', preventing accidental activation of debug features that previously caused performance overhead and execution pauses.

Highlights

  • Centralized Debug Logic: Introduced a new isDebugModeEnabled helper function in sandboxUtils.ts to standardize how debug mode is determined across the CLI.
  • Environment Variable Normalization: Replaced loose string truthiness checks (which incorrectly treated 'false' and '0' as true) with strict checks for 'true' or '1' in the DEBUG and DEBUG_MODE environment variables.
  • Improved Sandbox Stability: Prevented unintended side effects like unnecessary port publishing and --inspect-brk injection in Docker, Podman, and macOS environments when debugging is disabled.
  • Expanded Test Coverage: Added comprehensive unit tests to verify the new helper function and ensure sandbox behaviors correctly respect various DEBUG environment variable states.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces a helper function isDebugModeEnabled to consolidate debug mode checks across the CLI sandbox utility, checking both cliConfig and the DEBUG / DEBUG_MODE environment variables. It also adds corresponding unit tests. The feedback suggests refactoring the newly added tests in sandbox.test.ts to reduce duplication and improve maintainability by combining test cases using parameterized it.each blocks.

Comment thread packages/cli/src/utils/sandbox.test.ts Outdated
Comment on lines +1185 to +1339
it.each(['false', '0'])(
'should not publish the debug port when DEBUG=%s',
async (debugValue) => {
process.env['DEBUG'] = debugValue;
const config: SandboxConfig = createMockSandboxConfig({
command: 'docker',
image: 'gemini-cli-sandbox',
});

interface MockProcessWithStdout extends EventEmitter {
stdout: EventEmitter;
}
const mockImageCheckProcess =
new EventEmitter() as MockProcessWithStdout;
mockImageCheckProcess.stdout = new EventEmitter();
vi.mocked(spawn).mockImplementationOnce(() => {
setTimeout(() => {
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
mockImageCheckProcess.emit('close', 0);
}, 1);
return mockImageCheckProcess as unknown as ReturnType<typeof spawn>;
});

const mockSpawnProcess = new EventEmitter() as unknown as ReturnType<
typeof spawn
>;
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
if (event === 'close') {
setTimeout(() => cb(0), 10);
}
return mockSpawnProcess;
});
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);

await start_sandbox(config, [], undefined, ['arg1']);

const dockerArgs = vi.mocked(spawn).mock.calls[1]?.[1];
expect(dockerArgs).not.toContain('--publish');
expect(dockerArgs).not.toContain('9229:9229');
},
);

it.each(['true', '1'])(
'should publish the debug port when DEBUG=%s',
async (debugValue) => {
process.env['DEBUG'] = debugValue;
const config: SandboxConfig = createMockSandboxConfig({
command: 'docker',
image: 'gemini-cli-sandbox',
});

interface MockProcessWithStdout extends EventEmitter {
stdout: EventEmitter;
}
const mockImageCheckProcess =
new EventEmitter() as MockProcessWithStdout;
mockImageCheckProcess.stdout = new EventEmitter();
vi.mocked(spawn).mockImplementationOnce(() => {
setTimeout(() => {
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
mockImageCheckProcess.emit('close', 0);
}, 1);
return mockImageCheckProcess as unknown as ReturnType<typeof spawn>;
});

const mockSpawnProcess = new EventEmitter() as unknown as ReturnType<
typeof spawn
>;
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
if (event === 'close') {
setTimeout(() => cb(0), 10);
}
return mockSpawnProcess;
});
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);

await start_sandbox(config, [], undefined, ['arg1']);

const dockerArgs = vi.mocked(spawn).mock.calls[1]?.[1];
expect(dockerArgs).toContain('--publish');
expect(dockerArgs).toContain('9229:9229');
},
);

it.each(['false', '0'])(
'should not add --inspect-brk to seatbelt NODE_OPTIONS when DEBUG=%s',
async (debugValue) => {
vi.mocked(os.platform).mockReturnValue('darwin');
vi.mocked(fs.existsSync).mockReturnValue(true);
process.env['DEBUG'] = debugValue;

const config: SandboxConfig = createMockSandboxConfig({
command: 'sandbox-exec',
image: 'some-image',
});

interface MockProcess extends EventEmitter {
stdout: EventEmitter;
stderr: EventEmitter;
}
const mockSpawnProcess = new EventEmitter() as MockProcess;
mockSpawnProcess.stdout = new EventEmitter();
mockSpawnProcess.stderr = new EventEmitter();
vi.mocked(spawn).mockReturnValue(
mockSpawnProcess as unknown as ReturnType<typeof spawn>,
);

const promise = start_sandbox(config, [], undefined, ['arg1']);
setTimeout(() => {
mockSpawnProcess.emit('close', 0);
}, 10);

await expect(promise).resolves.toBe(0);

const spawnArgs = vi.mocked(spawn).mock.calls[0]?.[1];
const shellCmd = spawnArgs?.[spawnArgs.length - 1];
expect(shellCmd).not.toContain('--inspect-brk');
},
);

it.each(['true', '1'])(
'should add --inspect-brk to seatbelt NODE_OPTIONS when DEBUG=%s',
async (debugValue) => {
vi.mocked(os.platform).mockReturnValue('darwin');
vi.mocked(fs.existsSync).mockReturnValue(true);
process.env['DEBUG'] = debugValue;

const config: SandboxConfig = createMockSandboxConfig({
command: 'sandbox-exec',
image: 'some-image',
});

interface MockProcess extends EventEmitter {
stdout: EventEmitter;
stderr: EventEmitter;
}
const mockSpawnProcess = new EventEmitter() as MockProcess;
mockSpawnProcess.stdout = new EventEmitter();
mockSpawnProcess.stderr = new EventEmitter();
vi.mocked(spawn).mockReturnValue(
mockSpawnProcess as unknown as ReturnType<typeof spawn>,
);

const promise = start_sandbox(config, [], undefined, ['arg1']);
setTimeout(() => {
mockSpawnProcess.emit('close', 0);
}, 10);

await expect(promise).resolves.toBe(0);

const spawnArgs = vi.mocked(spawn).mock.calls[0]?.[1];
const shellCmd = spawnArgs?.[spawnArgs.length - 1];
expect(shellCmd).toContain('--inspect-brk');
},
);

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

While these tests are thorough and correctly validate the new DEBUG environment variable handling, there's a significant amount of duplicated setup code across the four new test cases. The test blocks for DEBUG='false'/'0' and DEBUG='true'/'1' are nearly identical for both the Docker and Seatbelt scenarios.

To improve maintainability and reduce redundancy, consider refactoring these tests to be more DRY (Don't Repeat Yourself). You could either:

  1. Use a single it.each for each scenario (Docker/Seatbelt): Combine the positive and negative cases into one parameterized test.
  2. Create a setup helper function: Extract the common mocking and setup logic into a reusable function that can be called by each test.

Here's an example of how you could combine the Docker tests:

it.each([
  { debugValue: 'false', shouldPublish: false },
  { debugValue: '0', shouldPublish: false },
  { debugValue: 'true', shouldPublish: true },
  { debugValue: '1', shouldPublish: true },
])('should publish debug port: $shouldPublish when DEBUG=$debugValue', async ({ debugValue, shouldPublish }) => {
  // ... common setup logic ...

  await start_sandbox(config, [], undefined, ['arg1']);

  const dockerArgs = vi.mocked(spawn).mock.calls[1]?.[1];
  if (shouldPublish) {
    expect(dockerArgs).toContain('--publish');
    expect(dockerArgs).toContain('9229:9229');
  } else {
    expect(dockerArgs).not.toContain('--publish');
    expect(dockerArgs).not.toContain('9229:9229');
  }
});

Applying a similar pattern to the Seatbelt tests would make the test suite more concise and easier to maintain in the future.

References
  1. When adding new functionality, consolidate it with existing similar logic to avoid duplication and improve maintainability.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Great catch! Refactored the tests to use a single parameterized it.each block for both Docker and Seatbelt scenarios to eliminate duplication and follow DRY principles. Thanks for the suggestion!

@gemini-cli gemini-cli Bot added the area/platform Issues related to Build infra, Release mgmt, Testing, Eval infra, Capacity, Quota mgmt label Aug 24, 2026
@Eswar809
Eswar809 force-pushed the fix/sandbox-debug-mode-parsing branch from f93398b to f3b3e1d Compare August 24, 2026 08:02
@gemini-cli

gemini-cli Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Hi there! Thank you for your interest in contributing to Gemini CLI.

To ensure we maintain high code quality and focus on our prioritized roadmap, we only guarantee review and consideration of pull requests for issues that are explicitly labeled as 'help wanted'.

This PR will be closed in 7 days if it remains without that designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding.

@gemini-cli

gemini-cli Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

This pull request is being closed as it has been open for 14 days without a 'help wanted' designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/platform Issues related to Build infra, Release mgmt, Testing, Eval infra, Capacity, Quota mgmt size/m A medium sized PR status/pr-nudge-sent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sandbox treats DEBUG=false and DEBUG=0 as enabled

1 participant