Skip to content

fix: ingest hang diagnostics — timeout detection + actionable error output#3

Merged
1a35e1 merged 1 commit into1a35e1:mainfrom
cosmicallycooked:fix/ingest-hang-diagnostics
Feb 23, 2026
Merged

fix: ingest hang diagnostics — timeout detection + actionable error output#3
1a35e1 merged 1 commit into1a35e1:mainfrom
cosmicallycooked:fix/ingest-hang-diagnostics

Conversation

@cosmicallycooked
Copy link
Copy Markdown
Contributor

Bug

sonar ingest tweets and sonar ingest bookmarks spin the loading indicator indefinitely when the Sonar server accepts the TCP connection but stalls before completing the response (e.g. under high load, misrouted traffic, or a cold-start delay). The only recovery is Ctrl+C. There is no feedback about what went wrong or what to try next.

The same silent-hang issue affects sonar ingest monitor --watch, where a single stalled poll freezes the entire watch loop.

Root cause

gql() in src/lib/client.ts passes no signal to fetch(), so requests have no timeout. Node's default TCP timeout is effectively infinite for established connections.

Fix

src/lib/client.ts

  • Every request is now wrapped in an AbortController with a configurable timeoutMs (default 20 s).
  • AbortError is caught and rethrown as a structured message that names the elapsed timeout, attributes the likely cause, and directs the operator to check SONAR_API_URL and retry.

src/commands/ingest/tweets.tsx + bookmarks.tsx

  • A component-level 15 s wall-clock deadline (via useRef / setTimeout) fires independently of the HTTP-layer timeout. This closes the gap where the gql() call times out but React hasn't surfaced the error yet.
  • Timeout state is tracked separately so the UI renders a yellow warning (not a red error) with a specific note: run sonar ingest monitor — the server may have queued the job even if the response was lost.
  • When the mutation returns false (job explicitly not queued), a follow-up hint directs the operator to sonar account.

src/commands/monitor.tsx

  • The raw fetch() call for /indexing/status now carries its own AbortController (10 s). In --watch mode a single stalled poll no longer freezes subsequent polls.

Verification

Reproduce the hang by pointing SONAR_API_URL at a server that accepts the connection but never responds (e.g. nc -l 9999):

SONAR_API_URL=http://localhost:9999/graphql sonar ingest tweets
# Before: spins forever
# After:  yellow warning after 15 s with actionable next steps

…ands

The root problem: fetch() has no built-in timeout. If the Sonar server
accepts a TCP connection but stalls before sending a response the spinner
renders indefinitely and the process must be killed manually.

Changes:

src/lib/client.ts
- gql() now wraps every request in an AbortController with a configurable
  timeoutMs (default 20 s). AbortError is caught and rethrown as a human-
  readable message that names the timeout, explains the likely cause, and
  directs the operator to check SONAR_API_URL and retry.

src/commands/ingest/tweets.tsx
src/commands/ingest/bookmarks.tsx
- Added a component-level 15 s wall-clock deadline (useRef + setTimeout)
  that fires independently of the fetch timeout. This catches the edge case
  where the request itself times out inside the gql() call but React has
  not yet had a chance to surface the error.
- Timeout state is tracked separately so the UI can render a yellow warning
  (rather than a red error) with a note to run 'sonar ingest monitor' —
  the server may have queued the job even if the response was lost.
- When the mutation returns false (job not queued) a follow-up hint
  directs the operator to check their account status.

src/commands/monitor.tsx
- The raw fetch() call for /indexing/status now uses its own
  AbortController (10 s). In --watch mode this prevents a single hung
  poll from freezing the entire watch loop.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 23, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Comment @coderabbitai help to get the list of available commands and usage tips.

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

2 participants