santifer/career-ops · error · Error

Apify run ${runId} did not finish within ${Math.round(timeou

Error message

Apify run ${runId} did not finish within ${Math.round(timeoutMs / 1000)}s${suffix}

What it means

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.

Source

Thrown at plugins/apify/_apify.mjs:169

        url,
        { headers: authHeaders(token) },
        Math.min(PER_REQUEST_TIMEOUT_MS, remainingMs),
      );
      const run = body?.data;
      if (run && TERMINAL_STATUSES.has(run.status)) return run;
      lastError = undefined;
    } catch (err) {
      // 4xx (401/403 auth revoked, 404 run not found) won't succeed on retry.
      if (err?.status >= 400 && err.status < 500) throw err;
      lastError = err;
    }
    const sleepMs = Math.min(POLL_INTERVAL_MS, deadline - Date.now());
    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 } = {}) {

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Increase the entry's `timeout_ms` in portals.yml (it is passed as timeoutMs to runActor).
  2. Check the `(last error: ...)` suffix — if it is a transient network error, retry the scan; if 4xx, fix auth.
  3. Review the actor's run on the Apify console to see why it is slow (input size, proxy usage, concurrency).
  4. Tune the actor input (fewer results, narrower search) so it finishes within the budget.

Example fix

# before — portals.yml
- name: indeed
  provider: apify
  actor: misceres/indeed-scraper
  # default timeout_ms (180s) too short for a full scrape
# after
- name: indeed
  provider: apify
  actor: misceres/indeed-scraper
  timeout_ms: 600000   # 10 minutes
Defensive patterns

Strategy: retry

Validate before calling

// Ensure timeout_ms is generous enough for the actor before scanning.
function assertAdequateTimeout(entry) {
  const ms = entry.timeout_ms ?? 180000;
  if (ms < 180000) {
    console.warn(`Entry '${entry.name}' timeout_ms=${ms} may be too short for this actor.`);
  }
}
portals.filter(p => p.provider === 'apify').forEach(assertAdequateTimeout);

Try / catch

async function runWithBackoff(actorId, input, opts, retries = 1) {
  try {
    return await runActor(actorId, input, opts);
  } catch (err) {
    if (/did not finish within/.test(err.message) && retries > 0) {
      return runActor(actorId, input, { ...opts, timeoutMs: (opts.timeoutMs ?? 180000) * 2 });
    }
    throw err;
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/f163a245e1840118. Report an issue: GitHub.