What happened?
The Gemini CLI hard-crashes with a synchronous AbortError when the LLM generates repetitive content that triggers the internal LoopDetectionService. This occurs in both interactive and non-interactive (headless) modes.
The crash is caused by a synchronous call to controller.abort() within the streaming loop of GeminiClient.processTurn. The @google/genai SDK wraps the underlying node-fetch stream in a custom async iterator (ReadableStreamToAsyncIterable). This custom wrapper detaches its 'error' listeners from the stream during yield suspensions (i.e., while the consuming loop is processing a chunk).
When abort() is called in this state, node-fetch synchronously emits an 'error' event. Finding no active listeners, Node.js triggers a fatal uncaught exception. Node's native AbortSignal (via EventTarget) intercepts this throw and schedules a fatal process.nextTick error, which bypasses all high-level try...catch blocks and terminates the process (Exit Code 1).
Furthermore, this crash renders the CLI's interactive confirmation logic unreachable, as the process terminates before the LoopDetected event can be effectively rendered by the UI layer.
Reproduction Steps
- Clone the repository and install dependencies (
npm install).
- Run the initial build:
npm run build.
- Execute the following command to trigger the crash:
npm start -- -p "Repeat the following sequence exactly 50 times: la li lu le lo"
Stack Trace
AbortError: The operation was aborted.
at AbortController.abort (node:internal/abort_controller:507:5)
at GeminiClient.processTurn (packages/core/src/core/client.ts)
...
What did you expect to happen?
The application should identify the loop, yield a LoopDetected event to the UI, and either:
- Stop the stream gracefully without crashing the process.
- Allow the CLI's interactive confirmation dialog to appear, enabling the user to choose whether to continue or disable loop detection for the session.
Client information
- CLI Version: 0.30.0-nightly.20260210.a2174751d
- Platform: Linux
- Node.js Version: v20.x+
Login information
Authenticated via cached credentials (OAuth/API Key).
Anything else we need to know?
This issue is a regression introduced in Commit 5a05fb0 (PR #8377), which added the controller.abort() call to save tokens but omitted the necessary safeguards for the resulting signal propagation in mixed-environment dependencies.
Related Issues
- Issue #10952: Documents the emergence of the
AbortError crash in v0.8.1+.
- Issue #18028: Highlights the Ink UI's vulnerability to hard crashes when encountering unhandled exceptions like this one.
- Issue #4878: Confirms that the system previously handled loops gracefully in v0.1.13 without crashing.
I have identified an approach that resolves this crash by ensuring the system exits the streaming loop before any abort logic is triggered. I'll be opening a PR shortly to implement this fix and am looking forward to discussing the implementation and any potential improvements with the team there!
What happened?
The Gemini CLI hard-crashes with a synchronous
AbortErrorwhen the LLM generates repetitive content that triggers the internalLoopDetectionService. This occurs in both interactive and non-interactive (headless) modes.The crash is caused by a synchronous call to
controller.abort()within the streaming loop ofGeminiClient.processTurn. The@google/genaiSDK wraps the underlyingnode-fetchstream in a custom async iterator (ReadableStreamToAsyncIterable). This custom wrapper detaches its'error'listeners from the stream during yield suspensions (i.e., while the consuming loop is processing a chunk).When
abort()is called in this state,node-fetchsynchronously emits an'error'event. Finding no active listeners, Node.js triggers a fatal uncaught exception. Node's nativeAbortSignal(viaEventTarget) intercepts this throw and schedules a fatalprocess.nextTickerror, which bypasses all high-leveltry...catchblocks and terminates the process (Exit Code 1).Furthermore, this crash renders the CLI's interactive confirmation logic unreachable, as the process terminates before the
LoopDetectedevent can be effectively rendered by the UI layer.Reproduction Steps
npm install).npm run build.npm start -- -p "Repeat the following sequence exactly 50 times: la li lu le lo"Stack Trace
What did you expect to happen?
The application should identify the loop, yield a
LoopDetectedevent to the UI, and either:Client information
Login information
Authenticated via cached credentials (OAuth/API Key).
Anything else we need to know?
This issue is a regression introduced in Commit
5a05fb0(PR #8377), which added thecontroller.abort()call to save tokens but omitted the necessary safeguards for the resulting signal propagation in mixed-environment dependencies.Related Issues
AbortErrorcrash in v0.8.1+.I have identified an approach that resolves this crash by ensuring the system exits the streaming loop before any abort logic is triggered. I'll be opening a PR shortly to implement this fix and am looking forward to discussing the implementation and any potential improvements with the team there!