hcengineering/platform · error · LinkPreviewError

TIMEOUT

TIMEOUT

Error message

Request timed out after ${timeoutMs}ms

What it means

LinkPreviewError with code TIMEOUT thrown by fetchWithTimeout when the AbortController fires before the HTTP response arrives. The fetch rejects with an AbortError, which is translated into a typed timeout error carrying the timeoutMs duration and the original error as cause.

Source

Thrown at pods/link-preview/src/parse.ts:289

// ============================================================================
// Fetch Utilities
// ============================================================================

async function fetchWithTimeout (url: string, options: RequestInit, timeoutMs: number): Promise<Response> {
  const controller = new AbortController()
  const timeoutId = setTimeout(() => {
    controller.abort()
  }, timeoutMs)

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    })
    return response
  } catch (error) {
    if (error instanceof Error && error.name === 'AbortError') {
      throw new LinkPreviewError(`Request timed out after ${timeoutMs}ms`, 'TIMEOUT', error)
    }
    throw new LinkPreviewError(
      `Failed to fetch URL: ${error instanceof Error ? error.message : 'Unknown error'}`,
      'FETCH_FAILED',
      error
    )
  } finally {
    clearTimeout(timeoutId)
  }
}

// ============================================================================
// oEmbed Functions
// ============================================================================

function findOEmbedProviderUrl (targetUrl: string): string | null {
  for (const provider of oembedProviders as OEmbedProvider[]) {
    const endpoints = provider.endpoints ?? []

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Increase the timeoutMs option passed to the fetch call
  2. Retry the request once with a longer timeout before giving up
  3. Check network egress/DNS from the runtime environment (curl -m <timeout> the URL)
  4. Verify the target host is actually reachable and not throttling your IP/rate limit
  5. Handle the TIMEOUT code explicitly and fall back to a cached or placeholder preview

Example fix

// before
const { response } = await fetchWithTimeout(url, {}, 2000)
// after
try {
  const { response } = await fetchWithTimeout(url, {}, 10000)
} catch (e) {
  if (e instanceof LinkPreviewError && e.code === 'TIMEOUT') {
    return getStalePreviewFromCache(url)
  }
  throw e
}
Defensive patterns

Strategy: try-catch

Type guard

function isTimeout(e: unknown): e is LinkPreviewError {
  return e instanceof LinkPreviewError && e.code === 'TIMEOUT'
}

Try / catch

try {
  await fetchWithTimeout(url, {}, timeoutMs)
} catch (e) {
  if (e instanceof LinkPreviewError && e.code === 'TIMEOUT') {
    return staleCacheFallback(url)
  }
  throw e
}

Prevention

When it happens

Trigger: The remote URL does not respond within timeoutMs (default configured in the preview fetch pipeline): slow origin server, stalled TLS handshake, network blackhole (packets dropped rather than refused), or a timeoutMs set too low.

Common situations: Previewing large or slow media sites on cold caches, environments with restrictive egress firewalls silently dropping traffic, DNS resolution delays, or callers passing an aggressive timeout for slow international sites.

Understand the failure class

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/5616486edfe583f5. Report an issue: GitHub.