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

  1. Inspect the per-item message and encode/validate the rendered URL before fetching (encodeURI/URL constructor).
  2. Lower mapConcurrent concurrency or add retry with backoff for rate-limited or flaky endpoints.
  3. Add timeout configuration to fetchSingle so slow items fail fast and predictable.
  4. 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

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


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/ffd1a706b6c5fc9f. Report an issue: GitHub.