koala73/worldmonitor · error
Timeout — scenario worker may be down
Error message
Timeout — scenario worker may be down
What it means
After the polling loop exhausts without receiving a 'done' (or 'failed') status, result is still unset and the code throws this error. It indicates the scenario worker neither completed nor explicitly failed within the polling window, so it may be down, overloaded, or the job was lost.
Solutions
- Check that the scenario worker is running and healthy (its health endpoint / deployment status).
- Retry the scenario; transient queue congestion often resolves on a second attempt.
- Reduce scenario size/complexity so it completes within the polling window.
- If the job is stuck, inspect worker logs/queue for the job id and restart the worker if necessary.
Example fix
// before
if (!result) throw new Error('Timeout — scenario worker may be down');
// after
if (!result) {
const healthy = await fetch(workerHealthUrl).then(r => r.ok).catch(() => false);
throw new Error(healthy ? 'Scenario timed out; try a smaller scenario' : 'Scenario worker unreachable');
} Defensive patterns
Strategy: retry
Try / catch
try {
const result = await runScenario(params);
} catch (e) {
if (e.message.includes('Timeout')) {
showBanner('Scenario worker slow or down — retrying…');
await retryWithBackoff(() => runScenario(params), { attempts: 2 });
} else throw e;
} Prevention
- Health-check the scenario worker before enqueuing large scenarios.
- Use smaller scenarios that fit the polling budget.
- Extend the poll window for known-large scenarios instead of failing fast.
When it happens
Trigger: Polling loop ends with result === null — every poll returned a non-terminal status (queued/running) or the loop ran out of iterations before 'done'/'failed' ever arrived.
Common situations: Scenario worker process down or not deployed; worker queue backed up under load making the job slower than the poll budget; network partition between client and status endpoint; very large scenario exceeding the built-in timeout.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Scenario failed
- COMPANY_MONITORING_${field}_INVALID
- COMPANY_MONITORING_CLASSIFICATION_FENCED
- callbackUrl DNS resolution failed: ${message}
- Authentication unavailable while loading MCP clients. Try ag
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/b8cfe822dd67edae.
Report an issue: GitHub.
Appendix: source
Thrown at src/components/SupplyChainPanel.ts:1182
let result: ScenarioResult | null = null;
// 60 × 1s = 60s max (worker typically completes in <1s). 1s poll keeps
// the perceived latency <2s in the common case. First iteration polls
// immediately (no sleep) in case the worker was already running on a
// previous job and blocked here only because of network round-trip.
for (let i = 0; i < 60; i++) {
if (signal.aborted) { resetButton('idle'); return; }
if (!this.content.isConnected) return; // panel gone — nothing to update
if (i > 0) await new Promise(r => setTimeout(r, 1000));
const status = await getScenarioStatus(jobId, { signal });
if (status.status === 'done') {
const r = status.result;
if (!r || !Array.isArray(r.topImpactCountries)) throw new Error('done without valid result');
result = r;
break;
}
if (status.status === 'failed') throw new Error('Scenario failed');
}
if (!result) throw new Error('Timeout — scenario worker may be down');
if (signal.aborted) { resetButton('idle'); return; }
if (!this.content.isConnected) return;
// After this callback fires, showScenarioSummary() → render() will rebuild
// the scenario-trigger DOM with the button already in its "Active" +
// disabled state (driven by activeScenarioState in renderChokepoints()).
// Do NOT touch the captured btn reference here — it's about to be detached
// by render()'s setContent(), and any imperative update would no-op
// silently while the fresh button shows the wrong state.
this.scenarioRunState.delete(scenarioId);
this.onScenarioActivate?.(scenarioId, result);
} catch (err) {
// Abort from a new click = user-triggered retry, no error banner needed.
if (err instanceof Error && err.name === 'AbortError') {
resetButton('idle');
return;
}
console.error('[scenario] run failed:', err);
resetButton('error');View on GitHub (pinned to 7d06c8633d)