jackwener/OpenCLI · warning
Batch fetch failed for ${itemUrl}: ${message}
Error message
Batch fetch failed for ${itemUrl}: ${message} What it means
In stepFetch's non-browser path, mapConcurrent runs fetchSingle per rendered URL. Any exception from fetchSingle (network error, timeout, HTTP failure) is caught, converted to a message via getErrorMessage, logged as this warning, and returned as { error: message } so one failing row doesn't abort the whole batch.
Source
Thrown at src/pipeline/steps/fetch.ts:133
if (page !== null) {
const results = await fetchBatchInBrowser(page, urls, method.toUpperCase(), renderedHeaders, concurrency);
for (let i = 0; i < results.length; i++) {
const r = results[i];
if (r && typeof r === 'object' && 'error' in r) {
log.warn(`Batch fetch failed for ${urls[i]}: ${(r as { error: string }).error}`);
}
}
return results;
}
// Non-browser: use concurrent pool (already optimized)
return mapConcurrent(data, concurrency, async (item, index) => {
const itemUrl = String(render(urlTemplate, { args, data, item, index }));
try {
return await fetchSingle(null, itemUrl, method, queryParams, headers, args, data);
} catch (error) {
const message = getErrorMessage(error);
log.warn(`Batch fetch failed for ${itemUrl}: ${message}`);
return { error: message };
}
});
}
const url = render(urlOrObj, { args, data });
return fetchSingle(page, String(url), method, queryParams, headers, args, data);
}
View on GitHub (pinned to 49907e53dc)
Solutions
- Inspect the per-item message and encode/validate the rendered URL before fetching (encodeURI/URL constructor).
- Lower mapConcurrent concurrency or add retry with backoff for rate-limited or flaky endpoints.
- Add timeout configuration to fetchSingle so slow items fail fast and predictable.
- Check items for missing fields before rendering (item.id etc.) and skip incomplete rows.
Example fix
// before
const itemUrl = String(render(urlTemplate, { args, data, item, index }));
// after
const rendered = render(urlTemplate, { args, data, item, index });
if (!rendered || !/^https?:\/\//.test(rendered)) {
return { error: `invalid rendered URL for item ${index}` };
}
const itemUrl = String(rendered); Defensive patterns
Strategy: type-guard
Validate before calling
const rendered = render(urlTemplate, { args, data, item, index });
if (!/^https?:\/\//.test(String(rendered))) throw new Error(`bad item URL at index ${index}`); Type guard
function isItemFetchFailure<T>(r: T | { error: string }): r is { error: string } {
return typeof r === 'object' && r !== null && 'error' in r;
} Try / catch
try {
return await fetchSingle(null, itemUrl, ...);
} catch (error) {
const message = getErrorMessage(error);
if (message.includes('timeout')) await sleep(500); // backoff before caller retry
return { error: message }; // keep the batch alive
} Prevention
- Check required item fields exist before template rendering.
- URI-encode interpolated path segments.
- Cap concurrency to avoid 429 rate limiting.
- Prefer explicit timeouts in fetchSingle over defaults.
When it happens
Trigger: fetchSingle throws for a data item — the rendered itemUrl is malformed (bad template interpolation), DNS/connection failure, request timeout, or the server returns a status fetchSingle treats as an error.
Common situations: Template placeholders missing from an item yielding 'undefined' in the URL; rate limiting (429) when concurrency is high; flaky third-party APIs; items built from user-supplied data with spaces/unsafe characters.
Related errors
- FETCH_ERROR
- archive search request failed: ${error?.message || error}
- archive wayback request failed: ${error?.message || error}
- ${label} request failed: ${err?.message ?? err}
- 字幕获取失败: ${err?.message || err}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/ffd1a706b6c5fc9f.
Report an issue: GitHub.