Skip to content

fix: abort generateText on webSearch timeout#304

Closed
sentry-junior[bot] wants to merge 1 commit intomainfrom
fix/websearch-timeout-abort
Closed

fix: abort generateText on webSearch timeout#304
sentry-junior[bot] wants to merge 1 commit intomainfrom
fix/websearch-timeout-abort

Conversation

@sentry-junior
Copy link
Copy Markdown
Contributor

@sentry-junior sentry-junior Bot commented May 7, 2026

Problem

JUNIOR-1HError: webSearch timed out (15 events, 10 users, ongoing since 2026-04-10)

When webSearch times out after 60s, the underlying generateText() call to the AI Gateway continues running in the background indefinitely. No AbortController signal is passed, so the HTTP request is never cancelled. Retries compound the problem since each one leaves behind another orphaned request leaking connections and memory.

Root Cause

withTimeout() uses Promise.race to implement the deadline, but the losing promise (generateText) is never told to stop. The same file (network.ts) already uses AbortController correctly for fetchTextWithRedirects — the search tool just wasn't wired the same way.

Fix

network.tswithTimeout

  • Added optional onTimeout callback for post-timeout cleanup.
  • Reject fires first, then onTimeout runs in a try/catch — so the abort can never race the timeout error, and a throwing cleanup callback cannot break timeout semantics.

search.tscreateWebSearchTool

  • Create an AbortController per search call.
  • Pass abortSignal: controller.signal to generateText() so the AI SDK cancels the underlying HTTP request on abort.
  • Wire onTimeout: () => controller.abort() so the abort fires immediately after the timeout rejection.

Tests

  • Added test verifying the AbortSignal is aborted on timeout.
  • Added test verifying the signal stays clean on successful search.
  • Added test verifying timeout is still correctly reported even when the abort cleanup throws.
  • Added abortSignal assertion to the happy-path call-shape test.

Closes JUNIOR-1H

@vercel
Copy link
Copy Markdown

vercel Bot commented May 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
junior-docs Ready Ready Preview, Comment May 7, 2026 6:39pm

Request Review

@sentry-junior sentry-junior Bot force-pushed the fix/websearch-timeout-abort branch from fdb848a to d19142c Compare May 7, 2026 18:39
@sentry-junior sentry-junior Bot changed the title fix: abort generateText on webSearch timeout and mark timeouts non-retryable fix: abort generateText on webSearch timeout May 7, 2026
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d19142c. Configure here.

}),
SEARCH_TIMEOUT_MS,
"webSearch",
{ onTimeout: () => controller.abort() },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Timeout errors still marked retryable despite PR intent

High Severity

The retryable computation on line 135 is !isAuthFailure(message), but the PR explicitly intends to change it to !timeout && !isAuthFailure(message). Without incorporating the timeout variable, timed-out searches are still marked retryable: true, so the retry amplification problem described in the PR (multiple 60s retries burning resources) remains unfixed. The test at line 159 correspondingly still asserts retryable: true instead of false.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d19142c. Configure here.

When webSearch times out after 60s, the underlying generateText() call
was left running in the background indefinitely — leaking connections
and memory. Retries compounded the problem since each retry left behind
another orphaned request.

Fix:
- Pass AbortController signal to generateText so the HTTP request is
  actually cancelled when the timeout fires.
- Add onTimeout callback to withTimeout for clean post-rejection
  cleanup. Reject fires first so abort can never race the timeout
  error; callback is wrapped in try/catch so cleanup failures cannot
  break timeout semantics.

Closes JUNIOR-1H

Co-Authored-By: Claude (anthropic/claude-opus-4.6) <noreply@anthropic.com>
@sentry-junior sentry-junior Bot closed this May 7, 2026
@sentry-junior sentry-junior Bot force-pushed the fix/websearch-timeout-abort branch from d19142c to 558a86e Compare May 7, 2026 18:58
dcramer pushed a commit that referenced this pull request May 7, 2026
## Problem

**JUNIOR-1H** — `Error: webSearch timed out` (15 events, 10 users,
ongoing since 2026-04-10)

When `webSearch` times out after 60s, the underlying `generateText()`
call to the AI Gateway continues running in the background indefinitely.
No `AbortController` signal is passed, so the HTTP request is never
cancelled. Retries compound the problem since each one leaves behind
another orphaned request leaking connections and memory.

## Root Cause

`withTimeout()` uses `Promise.race` to implement the deadline, but the
losing promise (`generateText`) is never told to stop. The same file
(`network.ts`) already uses `AbortController` correctly for
`fetchTextWithRedirects` — the search tool just wasn't wired the same
way.

## Fix

### `network.ts` — `withTimeout`
- Added optional `onTimeout` callback for post-timeout cleanup.
- **Reject fires first**, then `onTimeout` runs in a `try/catch` — so
the abort can never race the timeout error, and a throwing cleanup
callback cannot break timeout semantics.

### `search.ts` — `createWebSearchTool`
- Create an `AbortController` per search call.
- Pass `abortSignal: controller.signal` to `generateText()` so the AI
SDK cancels the underlying HTTP request on abort.
- Wire `onTimeout: () => controller.abort()` so the abort fires
immediately after the timeout rejection.

### Tests
- Added test verifying the `AbortSignal` is aborted on timeout.
- Added test verifying the signal stays clean on successful search.
- Added test verifying timeout is still correctly reported even when the
abort cleanup throws.
- Added `abortSignal` assertion to the happy-path call-shape test.

Supersedes #304 (closed due to shallow-clone force-push breaking branch
history).

Closes JUNIOR-1H

Co-authored-by: Junior <junior@sentry.io>
Co-authored-by: Claude (anthropic/claude-opus-4.6) <noreply@anthropic.com>
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.

0 participants