Skip to content

Protect stdout and stderr so JavaScript code can't accidentally write to stdout corrupting ink rendering#13247

Merged
jacob314 merged 4 commits intomainfrom
safe_stdout
Nov 20, 2025
Merged

Protect stdout and stderr so JavaScript code can't accidentally write to stdout corrupting ink rendering#13247
jacob314 merged 4 commits intomainfrom
safe_stdout

Conversation

@jacob314
Copy link
Copy Markdown
Contributor

@jacob314 jacob314 commented Nov 18, 2025

Summary

This avoids rendering artifacts particularly problematic in alternate buffer mode.

Also fix issue where running vim to edit a prompt or confirm a diff was corrupting the alternate buffer. Note that there was a bug and we were cleaning up when vim was used to confirm an edit.

Details

This pr is big but most changes are just fixing tests to expect the more modern libraries rather than watching for stdout.
Test changes can almost all be lightly skimmed as they are purely mechanical with no interesting deltas.

Similarly to how we monkey patched console.log we now monkey patch stdout and stderr. We get clever about ensuring we still write them out to the output when we know we are in non interactive mode and we write them to the debug console if the interactive mode was set.

Simplified console.log monkey patching as the code initially had multiple infinite loop bugs due to complexity of how we were doing this.

Things to test.
non-interactive mode still works with proper stdout even though we are trapping stdout at the beginning. Verify that output still streams fine.
Screenshot 2025-11-20 at 1 26 52 AM

Example show that debug output now gets through to the debug console in gemini cli even if it was rendered before entering the alternate buffer:

Screenshot 2025-11-20 at 1 46 35 AM

@jacob314 jacob314 requested a review from a team as a code owner November 18, 2025 03:07
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @jacob314, 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 significantly enhances the stability and integrity of the terminal UI by preventing arbitrary JavaScript code from directly writing to standard output and error streams. By introducing a robust interception layer for process.stdout and process.stderr, and providing dedicated bypass mechanisms for Ink's rendering, the changes eliminate rendering artifacts that could occur, particularly in alternate buffer mode. The ConsolePatcher and DebugProfiler have been refactored to align with this new centralized approach, ensuring a cleaner and more predictable terminal experience.

Highlights

  • Protected Standard Output/Error Streams: Implemented a new mechanism to protect process.stdout and process.stderr from accidental writes by JavaScript code. This prevents rendering artifacts, especially in alternate buffer mode, by redirecting all standard output/error to an internal logger.
  • Centralized Console Logging: The ConsolePatcher has been simplified and now emits console messages through coreEvents. The useConsoleMessages hook subscribes to these events, centralizing how console output is handled and displayed within the UI.
  • Dedicated Ink Output Streams: Introduced createInkStdio which provides stdout and stderr proxies specifically for Ink. These proxies bypass the newly patched process.stdout.write and process.stderr.write, ensuring Ink can render directly to the terminal without interference.
  • Refactored Terminal Interactions: All direct calls to process.stdout.write for terminal control sequences (e.g., setting window title, entering alternate screen, enabling bracketed paste, Kitty protocol detection) have been replaced with a new writeToStdout utility function, which uses the original, unpatched stdout.
  • Debug Profiler Integration: The DebugProfiler was updated to remove its direct stdout patching logic. It now relies on Ink's onRender callback to report frame renders and only processes frames when active, integrating seamlessly with the new stdio management.
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 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 counter productive. 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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

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.

Copy link
Copy Markdown
Contributor

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

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 robust mechanism to protect stdout and stderr from accidental writes that could corrupt the Ink-rendered UI. This is achieved by monkey-patching process.stdout.write and process.stderr.write to redirect output through a new centralized stdio utility. The changes are well-structured and significantly improve the application's resilience against UI corruption from stray logs.

The refactoring of console.* patching and debugLogger to use a coreEvents bus is a great architectural improvement, decoupling logging from the UI. Similarly, updating the DebugProfiler to rely on Ink's onRender callback instead of its own stdout patch makes frame counting more accurate.

I have one high-severity concern regarding the implementation in stdio.ts, where the patched stream write methods do not correctly handle backpressure. This could lead to performance issues or excessive memory usage under certain conditions. Please see the detailed comment.

Comment thread packages/cli/src/utils/stdio.ts
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Nov 18, 2025

Size Change: +5.33 kB (+0.03%)

Total Size: 21.1 MB

Filename Size Change
./bundle/gemini.js 21.1 MB +5.33 kB (+0.03%)
ℹ️ View Unchanged
Filename Size
./bundle/sandbox-macos-permissive-closed.sb 1.03 kB
./bundle/sandbox-macos-permissive-open.sb 890 B
./bundle/sandbox-macos-permissive-proxied.sb 1.31 kB
./bundle/sandbox-macos-restrictive-closed.sb 3.29 kB
./bundle/sandbox-macos-restrictive-open.sb 3.36 kB
./bundle/sandbox-macos-restrictive-proxied.sb 3.56 kB

compressed-size-action

return;
}
const now = Date.now();
// Simple frame detection logic (a write after at least 16ms is a new frame)
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.

this hacky logic depended on stdout as in't needed now that we get actual render events.


}, [setHistoryRemountKey, isAlternateBuffer, stdout]);
const handleEditorClose = useCallback(() => {
if (isAlternateBuffer) {
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.

this is the other core change forcing re-enter of alternate buffer mode when the editor is closed as vim exits it.

@jacob314 jacob314 force-pushed the safe_stdout branch 3 times, most recently from 4789cfc to 4ff11da Compare November 20, 2025 09:44
@jacob314 jacob314 requested a review from scidomino November 20, 2025 09:50
…hem.

This avoids rendering artifacts particularly problematic in alternate buffer mode.

Fix prop drilling for onEditorClose and call it when we spawn editors from text-buffer as well.

refactor: implement backlog for console logs in CoreEventEmitter

- Introduce _consoleLogBacklog to buffer console logs before listeners are attached.
- Create _emitOrQueue generic helper to handle backlog logic for both feedback and console logs.
- Rename drainFeedbackBacklog to drainBacklogs to reflect that it now flushes both backlogs.
- Update consumers and tests.

feat: add Output event with backlog to CoreEventEmitter

- Introduce OutputPayload and CoreEvent.Output.
- Add emitOutput method to broadcast stdout/stderr chunks.
- Implement _outputBacklog to buffer output when no listeners are present.
- Update drainBacklogs to flush output backlog.
- Add tests for Output event.

refactor: unify event backlogs into a single _eventBacklog in CoreEventEmitter

- Replace , , and  with .
- Store event type and payload in .
- Update  to push to the single backlog.
- Update  to process the unified backlog in FIFO order across all event types.

refactor: use strict types in CoreEventEmitter backlog to avoid any

- Introduce EventBacklogItem discriminated union.
- Update _eventBacklog to store EventBacklogItem.
- Update _emitOrQueue to use spread arguments matching generic emit.
- Update drainBacklogs to use strict cast for emit call.
@jacob314 jacob314 enabled auto-merge November 20, 2025 17:36
@jacob314 jacob314 disabled auto-merge November 20, 2025 18:43
@jacob314 jacob314 merged commit d1e35f8 into main Nov 20, 2025
19 of 23 checks passed
@jacob314 jacob314 deleted the safe_stdout branch November 20, 2025 18:44
thacio added a commit to thacio/auditaria that referenced this pull request Nov 23, 2025
…can't accidentally write to stdout corrupting ink rendering (google-gemini#13247)
mboshernitsan pushed a commit that referenced this pull request Nov 25, 2025
… to stdout corrupting ink rendering (#13247)

Bypassing rules as link checker failure is spurious.
danpalmer pushed a commit to danpalmer/gemini-cli that referenced this pull request Nov 29, 2025
… to stdout corrupting ink rendering (google-gemini#13247)

Bypassing rules as link checker failure is spurious.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants