Skip to content

fix(core): time out hanging web searches after 30 seconds - #29608

Open
ump45nose wants to merge 4 commits into
google-gemini:mainfrom
ump45nose:fix/web-search-timeout
Open

ump45nose wants to merge 4 commits into
google-gemini:mainfrom
ump45nose:fix/web-search-timeout

Conversation

@ump45nose

@ump45nose ump45nose commented Oct 2, 2026 •

Copy link
Copy Markdown

Summary

The GoogleSearch and WebFetch tools passed only the caller's abort signal to geminiClient.generateContent, so a request whose underlying LLM call never settled left the agent loop in a permanent Thinking... state (the issue reports 30+ minute hangs) until the user pressed Esc. This PR adds a 30 second execution timeout to both tools, as requested in the issue.

  • GoogleSearch: when the timer fires, the request is aborted and the tool returns a structured WEB_SEARCH_FAILED tool error, letting the model recover or inform the user.
  • WebFetch: the primary LLM-mediated fetch aborts on timeout and the existing all-or-nothing fallback takes over — the direct URL fetch path, which keeps its own per-URL timeout. External cancellation still stops the fallback, while a primary-fetch timeout does not.
  • Both tools share a new createTimeoutAbortHandle utility (packages/core/src/utils/abort.ts) that combines the external signal with the timeout and reports which one fired. It handles the case where ExecuteOptions.abortSignal is undefined.

Details

  • External aborts (user pressing Esc) are forwarded onto the internal controller, so user cancellation keeps its existing result (Web search was cancelled. for search) and remains distinguishable from a timeout via didTimeout().
  • Timer and listeners are cleaned up in finally blocks, including on the success path.
  • No new dependencies and no API changes; the timeouts are named constants (30s), inside the 15–30s range suggested in the issue.

Related Issues

Fixes #29594

How to Validate

  1. npx vitest run packages/core/src/utils/abort.test.ts packages/core/src/tools/web-search.test.ts packages/core/src/tools/web-fetch.test.ts — 79 tests pass, including 5 new utility tests, 2 new GoogleSearch tests (timeout → structured error; external abort before the timeout keeps the unchanged cancelled result), and 1 new WebFetch test (timed-out primary fetch falls back to the direct URL path and returns its result).
  2. Existing behavior is unchanged for successful searches/fetches and generic failures (all pre-existing tests pass unmodified).

Validation notes (honest scope): Prettier, ESLint, and tsc --noEmit pass on the touched files; the full packages/core suite and npm run preflight were not run locally — CI covers them.

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (8 new tests across the helper and both tools)
  • Noted breaking changes (none: successful/cancelled paths unchanged; only previously-hanging requests now time out)
  • Validated on required platforms/methods (pure async logic covered by unit tests):
    • MacOS
      • npm run
      • npx
      • Docker
      • Podman
      • Seatbelt
    • Windows
    • Linux

aggregate_evals.js logged "No reports found." and exited 0 when the
artifacts directory contained no report.json files. Since the nightly
workflow's Run Evals step is continue-on-error, a nightly whose matrix
legs all failed (e.g. expired API key) finished green.

On the scheduled nightly, zero reports now emit an ::error:: annotation
and exit non-zero so the run is visibly red. Filtered workflow_dispatch
runs can legitimately match nothing, so they keep exit 0 and emit a
::warning:: annotation instead; local runs stay quiet.
import.meta.dirname needs Node.js >= 20.11.0 while the repository
supports >= 20.0.0, so derive the directory from import.meta.url via
fileURLToPath instead, matching generate-settings-schema.test.ts.
The GoogleSearch tool passed only the caller's abort signal to
generateContent, so a search whose underlying request never settled
left the agent loop in a permanent "Thinking..." state until the user
cancelled manually.

The invocation now starts a 30 second timer and aborts the request
when it fires, returning a structured WEB_SEARCH_FAILED tool error so
the model can recover. External aborts are forwarded onto the same
controller, so user cancellation still reports "Web search was
cancelled." Timer and listener are cleaned up in a finally block.
@ump45nose
ump45nose requested review from a team as code owners October 2, 2026 09:39
@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 improves system reliability by introducing a timeout mechanism for web searches and enhancing error reporting in the evaluation aggregation pipeline. By ensuring that hanging web searches are properly aborted and that CI jobs correctly identify failures in nightly runs, the changes improve both the user experience and the maintainability of the CI/CD process.

Highlights

  • Web Search Timeout: Implemented a 30-second timeout for web search requests to prevent the agent loop from hanging indefinitely when a request fails to settle.
  • Evaluation Aggregator Improvements: Updated the evaluation aggregation script to fail explicitly during scheduled nightly runs if no reports are found, preventing false green statuses.
  • Enhanced Testing: Added new unit tests for the web search timeout behavior and integration tests for the evaluation aggregation script's exit conditions.
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/m A medium sized PR label Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

📊 PR Size: size/L

  • Lines changed: 402
  • Additions: +394
  • Deletions: -8
  • Files changed: 8

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

🛑 Action Required: Evaluation Approval

Steering changes have been detected in this PR. To prevent regressions, a maintainer must approve the evaluation run before this PR can be merged.

Maintainers:

  1. Go to the Workflow Run Summary.
  2. Click the yellow 'Review deployments' button.
  3. Select the 'eval-gate' environment and click 'Approve'.

Once approved, the evaluation results will be posted here automatically.

@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 30-second timeout mechanism for the web search tool to prevent the agent loop from hanging indefinitely, accompanied by new unit tests. It also updates the evaluation aggregation script to fail during scheduled nightly runs if no reports are found, and adds corresponding tests. The review feedback correctly identifies a critical issue where the optional abortSignal is accessed without safety checks, which could lead to runtime TypeError crashes during setup and cleanup of the abort event listeners.

Comment thread packages/core/src/tools/web-search.ts Outdated
Comment on lines +111 to +116
const onExternalAbort = () => timeoutController.abort(signal.reason);
if (signal.aborted) {
onExternalAbort();
} else {
signal.addEventListener('abort', onExternalAbort, { once: true });
}

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

The abortSignal parameter in ExecuteOptions is optional, meaning signal can be undefined at runtime. Accessing signal.reason, signal.aborted, or calling signal.addEventListener directly without a safety check will throw a TypeError and crash the tool execution. We should guard these accesses to ensure robustness. Additionally, the abort() method should not take any arguments.

    const onExternalAbort = () => timeoutController.abort();
    if (signal) {
      if (signal.aborted) {
        onExternalAbort();
      } else {
        signal.addEventListener('abort', onExternalAbort, { once: true });
      }
    }
References
  1. When consuming an object, if a property is optional in its type definition (interface), callers must handle the undefined case (e.g., by providing a default or optional chaining). Do not rely on the implementation details of the function that creates the object to always provide a value.
  2. The abort() method should not take arguments and should not throw an error. It should silently handle aborting the current stream.

Comment thread packages/core/src/tools/web-search.ts Outdated
};
} finally {
clearTimeout(timer);
signal.removeEventListener('abort', onExternalAbort);

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

If signal is undefined, calling signal.removeEventListener in the finally block will throw a TypeError. Use optional chaining to safely remove the event listener.

Suggested change
signal.removeEventListener('abort', onExternalAbort);
signal?.removeEventListener('abort', onExternalAbort);
References
  1. When consuming an object, if a property is optional in its type definition (interface), callers must handle the undefined case (e.g., by providing a default or optional chaining). Do not rely on the implementation details of the function that creates the object to always provide a value.

@gemini-cli gemini-cli Bot added priority/p1 Important and should be addressed in the near term. area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality labels Oct 2, 2026
Addresses review feedback and completes the coverage of google-gemini#29594:

- ExecuteOptions.abortSignal is optional, so the external signal is now
  handled defensively through createTimeoutAbortHandle.
- The primary LLM-mediated WebFetch path had the same never-settling
  request hazard as WebSearch; a timed-out primary fetch now falls back
  to the direct URL fetch path, which keeps its own per-URL timeout.
- The timer and listener logic moves into a shared
  createTimeoutAbortHandle utility with its own unit tests.

This branch is waiting to be deployed

1 waiting deployment
eval-gate — a86ad5f2 Waiting Oct 2, 2026 by ump45nose via Evaluate Steering & Regressions #2127
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality priority/p1 Important and should be addressed in the near term. size/l A large sized PR size/m A medium sized PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(tools): GoogleSearch tool hangs indefinitely in "Thinking..." state without timeout

1 participant