{"record":{"id":"f163a245e1840118","repo":"santifer/career-ops","slug":"apify-run-runid-did-not-finish-within-math-ro","errorCode":null,"errorMessage":"Apify run ${runId} did not finish within ${Math.round(timeoutMs / 1000)}s${suffix}","messagePattern":"Apify run (.+?) did not finish within (.+?)s(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/apify/_apify.mjs","lineNumber":169,"sourceCode":"        url,\n        { headers: authHeaders(token) },\n        Math.min(PER_REQUEST_TIMEOUT_MS, remainingMs),\n      );\n      const run = body?.data;\n      if (run && TERMINAL_STATUSES.has(run.status)) return run;\n      lastError = undefined;\n    } catch (err) {\n      // 4xx (401/403 auth revoked, 404 run not found) won't succeed on retry.\n      if (err?.status >= 400 && err.status < 500) throw err;\n      lastError = err;\n    }\n    const sleepMs = Math.min(POLL_INTERVAL_MS, deadline - Date.now());\n    if (sleepMs > 0) await sleep(sleepMs);\n  }\n  // Fire-and-forget cleanup; don't add abortRun's 5s to our wall-clock budget.\n  void abortRun(runId, token).catch(() => {});\n  const suffix = lastError ? ` (last error: ${lastError.message})` : '';\n  throw new Error(`Apify run ${runId} did not finish within ${Math.round(timeoutMs / 1000)}s${suffix}`);\n}\n\nasync function fetchDatasetItems(runId, token, deadline = null) {\n  const url = `${APIFY_API_BASE}/actor-runs/${runId}/dataset/items`;\n  const items = await fetchJson(\n    url,\n    { headers: authHeaders(token) },\n    PER_REQUEST_TIMEOUT_MS * 2,\n    CONNECT_RETRY_ATTEMPTS,\n    deadline,\n  );\n  if (!Array.isArray(items)) {\n    throw new Error(`Apify run ${runId} returned non-array dataset payload`);\n  }\n  return items;\n}\n\nexport async function runActor(actorId, input, { timeoutMs = DEFAULT_RUN_TIMEOUT_MS, token = process.env.APIFY_TOKEN } = {}) {","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/plugins/apify/_apify.mjs#L151-L187","documentation":"Thrown by `waitForRun` (plugins/apify/_apify.mjs:169) when the Apify actor run does not reach a terminal status (SUCCEEDED/FAILED/ABORTED/TIMED-OUT) before the shared deadline (`Date.now() >= deadline`, where deadline = start + timeoutMs). Before throwing, it fire-and-forgets `abortRun` to stop the actor and avoid wasting Apify credits. If there was a recurring poll error, its message is appended as `(last error: ...)`. The timeout is reported in seconds.","triggerScenarios":"An actor that genuinely takes longer than `timeoutMs` (default 180s); an actor stuck in READY/RUNNING; repeated transient poll errors (5xx/network) consuming the budget without a 4xx that would short-circuit. The while loop exits on deadline, then throws.","commonSituations":"Scraping a large job board with a slow actor exceeding the default 180s; a portal entry that sets a small `timeout_ms`; Apify platform slowness or queue backlog; network instability causing poll retries that eat the budget; an actor waiting on external proxies that stall.","solutions":["Increase the entry's `timeout_ms` in portals.yml (it is passed as timeoutMs to runActor).","Check the `(last error: ...)` suffix — if it is a transient network error, retry the scan; if 4xx, fix auth.","Review the actor's run on the Apify console to see why it is slow (input size, proxy usage, concurrency).","Tune the actor input (fewer results, narrower search) so it finishes within the budget."],"exampleFix":"# before — portals.yml\n- name: indeed\n  provider: apify\n  actor: misceres/indeed-scraper\n  # default timeout_ms (180s) too short for a full scrape\n# after\n- name: indeed\n  provider: apify\n  actor: misceres/indeed-scraper\n  timeout_ms: 600000   # 10 minutes","handlingStrategy":"retry","validationCode":"// Ensure timeout_ms is generous enough for the actor before scanning.\nfunction assertAdequateTimeout(entry) {\n  const ms = entry.timeout_ms ?? 180000;\n  if (ms < 180000) {\n    console.warn(`Entry '${entry.name}' timeout_ms=${ms} may be too short for this actor.`);\n  }\n}\nportals.filter(p => p.provider === 'apify').forEach(assertAdequateTimeout);","typeGuard":null,"tryCatchPattern":"async function runWithBackoff(actorId, input, opts, retries = 1) {\n  try {\n    return await runActor(actorId, input, opts);\n  } catch (err) {\n    if (/did not finish within/.test(err.message) && retries > 0) {\n      return runActor(actorId, input, { ...opts, timeoutMs: (opts.timeoutMs ?? 180000) * 2 });\n    }\n    throw err;\n  }\n}","preventionTips":["Set `timeout_ms` per actor based on observed run time, not the default.","Narrow actor input (fewer results) to keep runs within budget.","Watch the `(last error: ...)` suffix to distinguish slowness from a 4xx."],"tags":["apify","timeout","network","performance"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}