Skip to content

fix: --from-prompt timeout handling with actionable error output#4

Merged
1a35e1 merged 2 commits into1a35e1:mainfrom
cosmicallycooked:fix/from-prompt-timeout
Feb 23, 2026
Merged

fix: --from-prompt timeout handling with actionable error output#4
1a35e1 merged 2 commits into1a35e1:mainfrom
cosmicallycooked:fix/from-prompt-timeout

Conversation

@cosmicallycooked
Copy link
Copy Markdown
Contributor

Bug

sonar interests create --from-prompt and sonar interests update --from-prompt call OpenAI/Anthropic with no request timeout. The OpenAI path uses the web_search_preview tool, which can legitimately take 30–60 s on a healthy connection. Any network hiccup, provider slowdown, or rate-limit event causes the spinner to hang indefinitely with no feedback — the process must be killed manually.

The same issue affects callOpenAIReply / callAnthropicReply (used in interactive mode).

Root cause

All four fetch() call sites in src/lib/ai.ts pass no signal, so requests have no deadline.

Fix

src/lib/ai.ts

Introduced fetchWithTimeout(url, init, timeoutMs, vendorLabel) — a small wrapper that attaches an AbortController to every AI request. On AbortError it throws a structured message that:

  • Names the vendor and the elapsed timeout
  • Lists three likely causes (provider load, network, web search latency)
  • Suggests retrying or switching providers with --vendor

Per-vendor deadlines are generous to avoid false positives:

Vendor Timeout Reason
OpenAI 90 s web_search_preview adds significant latency
Anthropic 60 s Direct inference, no tool calls

Applied to all four call sites: callOpenAI, callAnthropic, callOpenAIReply, callAnthropicReply.

src/commands/interests/create.tsx + update.tsx

Spinner label for --from-prompt now includes the expected max wait time so operators can distinguish a slow-but-normal call from a genuine hang:

Generating interest via openai... (may take up to 90s with web search)

Verification

Reproduce by pointing the fetch at a server that stalls:

# Simulate a stalled OpenAI endpoint (not practical without mocking,
# but the timeout fires reliably under network isolation)
OPENAI_API_KEY=sk-test sonar interests create --from-prompt 'AI evals'
# Before: spinner runs forever
# After:  structured error after 90 s with vendor name, causes, and next steps

The timeout values (OPENAI_TIMEOUT_MS, ANTHROPIC_TIMEOUT_MS) are named constants in ai.ts for easy adjustment.

…rrors

The root problem: callOpenAI() and callAnthropic() in src/lib/ai.ts call
fetch() with no timeout. The OpenAI path uses the web_search_preview tool
which can take 30-60 s even on a healthy connection; any network hiccup or
provider slowdown causes the spinner to hang indefinitely.

Changes:

src/lib/ai.ts
- Added fetchWithTimeout() helper that wraps every AI fetch in an
  AbortController. Deadlines are set per-vendor:
    OpenAI  90 s  (web_search_preview adds latency)
    Anthropic 60 s
- AbortError is caught and rethrown as a structured message that names the
  vendor, the elapsed timeout, three likely causes, and the suggestion to
  retry or switch vendors with --vendor.
- Applied to all four call sites: callOpenAI, callAnthropic,
  callOpenAIReply, callAnthropicReply.

src/commands/interests/create.tsx
src/commands/interests/update.tsx
- Spinner label for --from-prompt now includes the max expected wait time
  so operators know the long wait is normal and not a hang:
    'Generating interest via openai... (may take up to 90s with web search)'
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds request deadlines and clearer operator feedback for AI-backed --from-prompt flows so the CLI doesn’t hang indefinitely on slow/stalled vendor responses.

Changes:

  • Introduces a fetchWithTimeout wrapper in src/lib/ai.ts and applies it to OpenAI/Anthropic interest generation + interactive reply generation calls.
  • Adds per-vendor timeout constants (OpenAI 90s, Anthropic 60s).
  • Updates the interests create/update --from-prompt spinner label to indicate expected maximum wait time.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
src/lib/ai.ts Adds a timeout wrapper around AI fetch() calls and applies it to all vendor request sites.
src/commands/interests/create.tsx Updates the --from-prompt spinner label to include an expected max wait time.
src/commands/interests/update.tsx Updates the --from-prompt spinner label to include an expected max wait time.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/ai.ts Outdated
Comment thread src/lib/ai.ts Outdated
Comment thread src/commands/interests/create.tsx
Comment thread src/commands/interests/create.tsx Outdated
Comment thread src/commands/interests/update.tsx
Comment thread src/commands/interests/update.tsx Outdated
- fetchWithTimeout now accepts a processResponse callback that wraps both
  the fetch() call and the body consumption (res.json()). The AbortController
  timer stays active until processResponse resolves or rejects, ensuring a
  stalled body download is caught by the same deadline as a stalled connection.
  clearTimeout moved to finally so the timer is always cleaned up.

- Timeout error message is now vendor-aware: the OpenAI web_search bullet is
  only appended when vendorLabel includes 'openai', avoiding misleading output
  when the failing vendor is Anthropic.

- OPENAI_TIMEOUT_MS and ANTHROPIC_TIMEOUT_MS are now exported from ai.ts.
  Spinner labels in create.tsx and update.tsx import these constants instead of
  hard-coding '90'/'60', and compute vendor via a single getVendor() call.
  The 'with web search' qualifier in the spinner is now conditional on
  vendor === 'openai' so Anthropic labels no longer mention web search.
@cosmicallycooked
Copy link
Copy Markdown
Contributor Author

@1a35e1 all threads resolved — ready for re-review.

Summary of changes pushed:

  • fetchWithTimeout: Refactored to accept a processResponse callback. The AbortController timer now covers the full response cycle (headers + body). clearTimeout moved to finally for guaranteed cleanup.
  • Timeout error message: Now vendor-aware — the web_search bullet only appears for OpenAI.
  • Spinner labels (create.tsx + update.tsx): OPENAI_TIMEOUT_MS/ANTHROPIC_TIMEOUT_MS exported from ai.ts and imported here. getVendor() called once per render. Timeout values derived from the shared constants. 'with web search' qualifier is now conditional on vendor === 'openai'.

@1a35e1 1a35e1 merged commit e7f5ca0 into 1a35e1:main Feb 23, 2026
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.

3 participants