fix(core): handle GUI editor non-zero exit codes gracefully#20376
fix(core): handle GUI editor non-zero exit codes gracefully#20376jacob314 merged 6 commits intogoogle-gemini:mainfrom
Conversation
GUI editors like Zed and VS Code can legitimately exit with non-zero codes under normal circumstances (e.g., window closed while workspace is loading). Previously, this caused an unhandled promise rejection that crashed the CLI process. Changes: - Resolve the promise and log a warning instead of rejecting when a GUI editor exits with a non-zero code - Emit CoreEvent.ExternalEditorClosed in the GUI editor path (on both success and failure), consistent with the terminal editor path - Still reject on actual spawn errors (e.g., editor binary not found) Fixes google-gemini#13870
Summary of ChangesHello @reyyanxahmed, 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 enhances the robustness of the CLI's external editor integration by gracefully handling non-zero exit codes from GUI editors, which previously led to unhandled promise rejections and crashes. It also standardizes the emission of the Highlights
Changelog
Activity
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
Code Review
This pull request correctly addresses a crash caused by unhandled promise rejections when a GUI editor exits with a non-zero status code. The approach of resolving the promise and logging a warning is a good solution. The changes also ensure the ExternalEditorClosed event is consistently emitted, which is a great improvement for UI state management. I've added one comment to make the event handling more robust against a potential race condition where both error and close events could fire for a single failure, preventing duplicate event emissions and ensuring the promise is settled only once.
| childProcess.on('close', (code) => { | ||
| if (code === 0) { | ||
| resolve(); | ||
| } else { | ||
| reject(new Error(`${editor} exited with code ${code}`)); | ||
| if (code !== 0) { | ||
| // GUI editors (VS Code, Zed, etc.) can exit with non-zero codes | ||
| // under normal circumstances (e.g., window closed while loading). | ||
| // Log a warning instead of crashing the CLI process. | ||
| debugLogger.warn(`${editor} exited with code ${code}`); | ||
| } | ||
| coreEvents.emit(CoreEvent.ExternalEditorClosed); | ||
| resolve(); | ||
| }); | ||
|
|
||
| childProcess.on('error', (error) => { | ||
| coreEvents.emit(CoreEvent.ExternalEditorClosed); | ||
| reject(error); | ||
| }); |
There was a problem hiding this comment.
There's a potential race condition here. According to Node.js documentation, the close event can be emitted after an error event. In such a scenario, both event handlers would run. This would cause coreEvents.emit(CoreEvent.ExternalEditorClosed) to be called twice and would attempt to settle the promise twice (first rejecting, then resolving). While attempting to resolve an already-rejected promise is a no-op, emitting the ExternalEditorClosed event twice could lead to unintended side effects in the UI if the listener is not idempotent. To make this more robust, you should ensure that the logic for settling the promise and emitting the event runs only once by using a flag. This approach aligns with the principle of using explicit state variables to manage the state of asynchronous operations, as outlined in our repository's code review guidelines.
let isSettled = false;
childProcess.on('close', (code) => {
if (isSettled) return;
isSettled = true;
if (code !== 0) {
// GUI editors (VS Code, Zed, etc.) can exit with non-zero codes
// under normal circumstances (e.g., window closed while loading).
// Log a warning instead of crashing the CLI process.
debugLogger.warn(`${editor} exited with code ${code}`);
}
coreEvents.emit(CoreEvent.ExternalEditorClosed);
resolve();
});
childProcess.on('error', (error) => {
if (isSettled) return;
isSettled = true;
coreEvents.emit(CoreEvent.ExternalEditorClosed);
reject(error);
});References
- When managing the state of asynchronous operations, rely on an explicit state variable (e.g., a state enum) rather than checking for the existence of a promise object. Promise objects may be cleared in
finallyblocks upon completion, making them unreliable for state checks after the operation has finished.
There was a problem hiding this comment.
/gemini-code-assist could you check the latest fix I pushed "862db1b"? Here's what changed:
Added an [isSettled] flag in the GUI editor path of [openDiff] so that if Node.js fires both [error] and close for the same child process failure, only the first handler runs. This prevents [CoreEvent.ExternalEditorClosed] from being emitted twice and the promise from being settled twice.
Added 5 new tests (one per GUI editor) covering the race condition where both events fire sequentially, asserting that [ExternalEditorClosed] is emitted exactly once.
All 160 tests pass.
There was a problem hiding this comment.
Yes, the changes in commit 862db1b correctly address the potential race condition by introducing the isSettled flag. This ensures that the promise is settled and CoreEvent.ExternalEditorClosed is emitted only once, even if both error and close events fire. The addition of dedicated tests for this specific race condition is also excellent and provides robust validation for the fix.
… fire Add isSettled flag to prevent ExternalEditorClosed from being emitted twice and the promise from being settled twice when Node.js fires both error and close events for the same child process failure. Also adds a test per GUI editor covering this race condition.
…emini#20376) Co-authored-by: Jacob Richman <jacob314@gmail.com>
…emini#20376) Co-authored-by: Jacob Richman <jacob314@gmail.com>
Co-authored-by: Jacob Richman <jacob314@gmail.com>
…emini#20376) Co-authored-by: Jacob Richman <jacob314@gmail.com>

Summary
Fixes #13870
GUI editors like Zed, VS Code, Cursor, and others can legitimately exit with non-zero codes under normal circumstances (e.g., window closed while a workspace is loading, or the editor's internal process lifecycle). Previously, this caused an unhandled promise rejection that crashed the entire CLI process.
Changes
packages/core/src/utils/editor.tsclosehandler: Resolve the promise and log a debug warning viadebugLogger.warn()instead of rejecting when the editor exits with a non-zero code. This prevents the CLI from crashing.CoreEvent.ExternalEditorClosed: The GUI editor path now emits this event on both thecloseanderrorhandlers, consistent with the terminal editor path (which already did this in afinallyblock). This ensures the UI properly restores its state after the editor closes regardless of exit code.packages/core/src/utils/editor.test.tsCoreEvent.ExternalEditorClosedemission on: successful exit, non-zero exit, and spawn error for all GUI editors.Root Cause
The
openDifffunction had two code paths:ExternalEditorClosed?finally)finallyblock)After this change, both paths handle non-zero exits gracefully and always emit
ExternalEditorClosed.Testing
All 155 tests in
editor.test.tspass.