agalwood/Motrix · error · HttpError
plugin.http.timeout
plugin.http.timeout
Error message
Request timed out after ${timeoutMs}ms What it means
A setTimeout timer fires after timeoutMs (clamped to between 1_000ms and 300_000ms) and aborts the internal controller with reason 'timeout'. The undici request rejects, the catch block matches the reason, and the timeout is surfaced as a typed HttpError rather than a generic AbortError. timeoutMs comes from opts.timeoutMs or the host default.
Source
Thrown at src/core/plugin/capabilities/http.ts:334
// different host.
delete reqHeaders.cookie
}
}
let response: Awaited<ReturnType<typeof undiciRequest>>
try {
response = await undiciRequest(currentUrl, {
method,
headers: reqHeaders,
body: bodyPayload,
signal: internalCtrl.signal,
dispatcher,
})
} catch (err: unknown) {
if (internalCtrl.signal.aborted) {
const reason = internalCtrl.signal.reason
if (reason === 'timeout') {
throw new HttpError(
'plugin.http.timeout',
`Request timed out after ${timeoutMs}ms`
)
}
if (reason === 'plugin_abort') {
throw new HttpError(
'plugin.http.aborted',
'Request aborted by plugin'
)
}
throw new HttpError('plugin.http.aborted', 'Request aborted')
}
if (
err instanceof Error &&
(err.name === 'AbortError' ||
err.name === 'DOMException' ||
err.constructor?.name === 'DOMException')
) {View on GitHub (pinned to 1a708ee577)
Solutions
- Raise opts.timeoutMs up to the 300_000ms clamp ceiling where the endpoint legitimately needs it.
- Move large transfers to a background job and poll, instead of blocking one HTTP call.
- Retry with exponential backoff for transient slowness.
- Optimize the server endpoint's p99 latency.
Example fix
// before
await http.request({ url, responseType: 'json', timeoutMs: 1000 })
// after
await http.request({ url, responseType: 'json', timeoutMs: 30000 }) Defensive patterns
Strategy: retry
Validate before calling
const timeoutMs = Math.min(300_000, Math.max(1_000, expectedLatencyMs * 3))
await http.request({ url, responseType: 'json', timeoutMs }) Try / catch
for (const attempt of [1,2,3]) {
try {
return await http.request({ url, responseType: 'json', timeoutMs })
} catch (e) {
if (e instanceof HttpError && e.code === 'plugin.http.timeout' && attempt < 3) {
await sleep(2 ** attempt * 200); continue
}
throw e
}
} Prevention
- Set opts.timeoutMs based on the endpoint's known p99 latency, not a guess.
- Surface timeout as a retryable condition in your error layer.
- Move transfers longer than 300s (the clamp ceiling) to a background job + polling pattern.
When it happens
Trigger: Server response slower than timeoutMs; timeoutMs explicitly set too low; network stall mid-stream; large upload that cannot finish in the window.
Common situations: Default timeout too aggressive for slow endpoints; plugin does not override opts.timeoutMs and inherits a small host default; cold-start latency on a serverless target.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- GeoIPDownloadFailed
- manifest fetch failed: HTTP ${res.status}
- manifest too large: ${text.length} > ${max}
- plugin.http.network
- plugin.lifecycle.deactivate_timeout
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/94373e95bc8d9acc.
Report an issue: GitHub.