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
- 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.
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
- 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.
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Pinned model timed out after ${MODEL_TIMEOUT_MS / 1000}s
- Timeout after ${MODEL_TIMEOUT_MS / 1000}s
- Apify did not return a run id: ${JSON.stringify(body).slice(
- apify: invalid timeoutMs ${JSON.stringify(timeoutMs)} (must
- ${describeGitCommand(args)} timed out after ${timeoutSeconds
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/f163a245e1840118.
Report an issue: GitHub.