santifer/career-ops · error · Error
Apify run ${runId} returned non-array dataset payload
Error message
Apify run ${runId} returned non-array dataset payload What it means
Thrown by `fetchDatasetItems` (plugins/apify/_apify.mjs:182) when the GET to `/actor-runs/<runId>/dataset/items` returns a payload that is not a JSON array. Apify's dataset endpoint is expected to return a bare JSON array of item objects; a non-array (object envelope, paginated wrapper, empty object, or error body) means the result cannot be consumed as items, so it fails rather than silently treating it as zero results.
Source
Thrown at plugins/apify/_apify.mjs:182
if (sleepMs > 0) await sleep(sleepMs);
}
// Fire-and-forget cleanup; don't add abortRun's 5s to our wall-clock budget.
void abortRun(runId, token).catch(() => {});
const suffix = lastError ? ` (last error: ${lastError.message})` : '';
throw new Error(`Apify run ${runId} did not finish within ${Math.round(timeoutMs / 1000)}s${suffix}`);
}
async function fetchDatasetItems(runId, token, deadline = null) {
const url = `${APIFY_API_BASE}/actor-runs/${runId}/dataset/items`;
const items = await fetchJson(
url,
{ headers: authHeaders(token) },
PER_REQUEST_TIMEOUT_MS * 2,
CONNECT_RETRY_ATTEMPTS,
deadline,
);
if (!Array.isArray(items)) {
throw new Error(`Apify run ${runId} returned non-array dataset payload`);
}
return items;
}
export async function runActor(actorId, input, { timeoutMs = DEFAULT_RUN_TIMEOUT_MS, token = process.env.APIFY_TOKEN } = {}) {
if (!token) throw new Error('APIFY_TOKEN not set');
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
throw new Error(`apify: invalid timeoutMs ${JSON.stringify(timeoutMs)} (must be a positive finite number of milliseconds)`);
}
// Single deadline shared across startRun → waitForRun → fetchDatasetItems so
// the caller's timeoutMs is the end-to-end ceiling, not just the wait loop.
const deadline = Date.now() + timeoutMs;
const runId = await startRun(actorId, input, token, deadline);
const run = await waitForRun(runId, token, deadline, timeoutMs);
if (run.status !== 'SUCCEEDED') {
const reason = run.statusMessage ? `: ${run.statusMessage}` : '';
throw new Error(`Apify actor ${actorId} finished with status ${run.status}${reason}`);
}View on GitHub (pinned to 9b17a8ac97)
Solutions
- Inspect the actor run on the Apify console — confirm the dataset actually has items and the expected shape.
- Check for Apify v2 API changes to the dataset/items endpoint (envelope vs bare array).
- Retry once; if a proxy is intercepting, bypass it for api.apify.com.
- If the API schema changed, update fetchDatasetItems to unwrap the envelope.
Defensive patterns
Strategy: try-catch
Try / catch
try {
const items = await runActor(actorId, input, opts);
} catch (err) {
if (/non-array dataset payload/.test(err.message)) {
console.error(`Apify dataset shape changed for ${actorId} — possible API drift. ${err.message}`);
// do not blindly coerce; investigate the actual payload
} else throw err;
} Prevention
- Keep the apify plugin updated to track dataset endpoint schema.
- Bypass intercepting proxies for api.apify.com to avoid captive-portal JSON.
When it happens
Trigger: After a SUCCEEDED run, the dataset fetch returns something like `{ data: [...] }` (envelope), a paginated object, or an error JSON object. `Array.isArray(items)` is false, so the guard throws.
Common situations: Apify API version drift returning an envelope instead of a bare array; a dataset endpoint change to pagination by default; an intercepting proxy/captive portal returning a JSON error object; the run's dataset being empty but returned as `{}` instead of `[]`.
Related errors
- Apify did not return a run id: ${JSON.stringify(body).slice(
- Apify actor ${actorId} finished with status ${run.status}${r
- a16z-speedrun-talent: unexpected API response on page ${page
- agentic-jobs: unexpected API response shape on page ${page}
- agentic-jobs: parsed 0 jobs from the API — the response shap
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/b2729b4610d17ea1.
Report an issue: GitHub.