Skip to content

[SSR Agent] Issue Fix (22588): Fix silent hang in MessageBus.request when publish fails - #28816

Closed
joneba-google wants to merge 2 commits into
google-gemini:mainfrom
JonE01:ssr-agent-22588
Closed

joneba-google wants to merge 2 commits into
google-gemini:mainfrom
JonE01:ssr-agent-22588

Conversation

@joneba-google

Copy link
Copy Markdown
Contributor

fixes #22588
Original issue URL: #22588

Context & Problem

In MessageBus.request(), calling this.publish() was a floating promise without any register of failure. If publish() rejected, the promise would silently hang for 60 seconds (waiting for a timeout) or cause uncaught process crashes.

Detailed Changes

  • packages/core/src/confirmation-bus/message-bus.ts:
    Chained .catch() on this.publish() to clear timeout, unsubscribe handler via cleanup(), and reject the promise with the caught error.
  • packages/core/src/confirmation-bus/message-bus.test.ts:
    Added a unit test using Vitest asserting that request() rejects immediately when publish() rejects.

Verification

All unit tests passed successfully. The ESLint check passed on all modified files without errors.

@joneba-google
joneba-google requested a review from a team as a code owner August 14, 2026 20:56
@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 PR addresses a bug where MessageBus.request() would hang indefinitely if the underlying publish() call failed. By attaching an error handler to the promise, the system now correctly cleans up resources and rejects the request promise upon failure, improving reliability and error reporting.

Highlights

  • Error Handling in MessageBus: Added a .catch() block to the publish() call in MessageBus.request() to properly handle failures and prevent silent hangs.
  • Unit Testing: Added a new test case to verify that request() rejects immediately when publish() fails.
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. ↩

@github-actions github-actions Bot added the size/s A small PR label Aug 14, 2026
@github-actions

github-actions Bot commented Aug 14, 2026 •

Copy link
Copy Markdown

📊 PR Size: size/M

  • Lines changed: 163
  • Additions: +140
  • Deletions: -23
  • Files changed: 2

@google-cla

google-cla Bot commented Aug 14, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@github-actions

Copy link
Copy Markdown

You already have 7 pull requests open. Please work on getting existing PRs merged before opening more.

@github-actions github-actions Bot closed this Aug 14, 2026

@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 adds error handling to the request method in MessageBus by catching publication errors and rejecting the request promise, along with a corresponding unit test. However, the reviewer points out that MessageBus.publish() catches errors internally and resolves successfully (emitting an 'error' event), meaning the added .catch() block will not be triggered in production and the promise will still hang. Additionally, the unit test mocks publish to reject, which does not accurately reflect this production behavior. The reviewer also notes that asynchronous operations waiting for user input should rely on the provided AbortSignal for cancellation rather than a separate timeout.

Comment on lines +260 to +265
this.publish({ ...request, correlationId } as TRequest).catch(
(error: unknown) => {
cleanup();
reject(error);
},
);

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

In MessageBus.publish(), all errors are caught internally and emitted via this.emit('error', error). If there is an 'error' listener registered on the MessageBus (which is standard practice in production for logging/monitoring), this.emit will execute successfully and the promise returned by this.publish() will resolve rather than reject. Because of this, the .catch() block added here in request() will never be triggered when a publication error occurs (such as a policy check failure or invalid message structure). The request() promise will still silently hang. Additionally, asynchronous operations waiting for user input via the MessageBus should rely on the provided AbortSignal for cancellation, rather than implementing a separate timeout, to maintain consistency with existing patterns.

References
  1. Asynchronous operations waiting for user input via the MessageBus should rely on the provided AbortSignal for cancellation, rather than implementing a separate timeout, to maintain consistency with existing patterns.

Comment on lines +456 to +471
it('should reject immediately if publish fails/rejects', async () => {
const publishError = new Error('Publish failed');
vi.spyOn(messageBus, 'publish').mockRejectedValue(publishError);

const request: Omit<ToolConfirmationRequest, 'correlationId'> = {
type: MessageBusType.TOOL_CONFIRMATION_REQUEST,
toolCall: { name: 'test-tool', args: {} },
};

const requestPromise = messageBus.request<
ToolConfirmationRequest,
ToolConfirmationResponse
>(request, MessageBusType.TOOL_CONFIRMATION_RESPONSE, 2000);

await expect(requestPromise).rejects.toThrow('Publish failed');
});

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

This test mocks messageBus.publish to reject using mockRejectedValue. However, in production, publish() catches all errors internally and resolves successfully (only emitting an 'error' event) if there is an 'error' listener registered. Therefore, this test does not accurately reflect production behavior where request() will still hang. Consider updating publish() to rethrow errors so that this test scenario matches real-world behavior.

@joneba-google joneba-google reopened this Aug 17, 2026
@gemini-cli gemini-cli Bot added priority/p2 Important but can be addressed in a future release. area/core Issues related to User Interface, OS Support, Core Functionality 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item. labels Aug 17, 2026
…and add AbortSignal support

$fixes google-gemini#22588

### Context & Problem

In `MessageBus.request()`, calling `this.publish()` was a floating promise without failure handling. Additionally, `MessageBus.publish()` was swallowing all errors inside its internal catch block without rethrowing, causing `publish()` to resolve cleanly when errors occurred (such as policy check or invalid message errors), which caused `request()` to silently hang. Furthermore, `request()` lacked support for `AbortSignal` for standardized cancellation.

### Detailed Changes

* **packages/core/src/confirmation-bus/message-bus.ts**:
  - Updated `publish()` to rethrow errors after emitting `'error'`, ensuring that publication failures reject the returned promise.
  - Updated `request()` to catch publication failures and reject immediately, avoiding silent hangs.
  - Added support for `AbortSignal` in `request()` via `timeoutMsOrOptions?: number | { timeoutMs?: number; signal?: AbortSignal }` for cooperative cancellation.
* **packages/core/src/confirmation-bus/message-bus.test.ts**:
  - Updated tests to verify that `publish()` rejects on policy/validation failures.
  - Added unit tests for `request()` publication rejection, internal error rejection, and `AbortSignal` cancellation (both live abort and pre-aborted signals).

### Verification

* Verified with full Vitest unit test suite (`21 tests passed` in `message-bus.test.ts`).
* Verified full build (`npm run build`), typecheck (`npm run typecheck`), and ESLint check passed cleanly.
@github-actions github-actions Bot added the size/m A medium sized PR label Aug 17, 2026
@joneba-google

Copy link
Copy Markdown
Contributor Author

/gemini review

@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 enhances the MessageBus class by adding support for AbortSignal in the request method to allow request cancellation, and ensures that publish failures are caught and rejected. It also updates publish to rethrow caught errors. The review feedback points out a potential issue where emitting an 'error' event without active listeners in Node.js can synchronously throw an ERR_UNHANDLED_ERROR, bypassing the original error, and suggests checking listenerCount before emitting.

Comment on lines 162 to 165
} catch (error) {
this.emit('error', error);
throw error;
}

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

In Node.js, emitting an 'error' event when there are no registered listeners causes the EventEmitter to throw an ERR_UNHANDLED_ERROR synchronously. Since this happens inside the catch block, it bypasses the original error and propagates the unhandled error wrapper to the caller of publish or request.

To preserve the original error for the caller while still supporting global error logging when listeners are present, check if there are any 'error' listeners before emitting.

    } catch (error) {
      if (this.listenerCount('error') > 0) {
        this.emit('error', error);
      }
      throw error;
    }

Comment thread packages/core/src/confirmation-bus/message-bus.ts Outdated

@adamfweidman adamfweidman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Too complicated to approve without deeper investigation

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

Labels

area/core Issues related to User Interface, OS Support, Core Functionality 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item. priority/p2 Important but can be addressed in a future release. size/m A medium sized PR size/s A small PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(core): MessageBus.request() silently hangs 60s when publish() fails

2 participants