koala73/worldmonitor · error
Scenario failed
Error message
Scenario failed
What it means
While polling a supply-chain scenario's status, a response with status 'failed' is treated as a terminal worker-side failure and surfaced as this generic Error. It means the backend/worker ran the scenario and reported failure, as opposed to timeout (no result in time) or a malformed done payload.
Solutions
- Retry the scenario — many worker failures are transient (restarts, upstream hiccups).
- Check the scenario worker's logs for the corresponding job to find the underlying failure.
- Simplify or adjust scenario parameters (smaller region/fewer layers) in case the specific input triggers the failure.
- Verify worker health/deployment status; if persistent, file an issue with the scenario id.
Example fix
// before
if (status.status === 'failed') throw new Error('Scenario failed');
// after
if (status.status === 'failed') {
if (attempt < 2) { await sleep(backoff); continue poll; }
throw new Error(`Scenario failed: ${status.error ?? 'unknown worker error'}`);
} Defensive patterns
Strategy: retry
Type guard
function isScenarioStatus(s: unknown): s is { status: 'done' | 'failed' | string; result?: { topImpactCountries: unknown[] } } {
return typeof s === 'object' && s !== null && 'status' in s;
} Try / catch
try {
const result = await runScenario(params);
} catch (e) {
if (e.message === 'Scenario failed') {
await retryWithBackoff(() => runScenario(params), { attempts: 2 });
} else throw e;
} Prevention
- Retry failed scenarios once or twice with backoff before surfacing an error.
- Keep scenario parameters within supported ranges.
- Monitor worker health and alert on rising 'failed' rates.
When it happens
Trigger: Polling loop receives a status object where status.status === 'failed' — the scenario worker processed the request but its computation errored or was marked failed server-side.
Common situations: Worker crashed mid-computation for this particular scenario; upstream data source for the scenario unavailable; scenario parameters that hit an unsupported code path; transient backend deploy/restart.
Related errors
- Timeout — scenario worker may be down
- COMPANY_MONITORING_${field}_INVALID
- Scenario queue is at capacity, please try again later
- Failed to enqueue scenario job
- Unknown scenario: ${scenarioId}
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/ff024e1926ac73c0.
Report an issue: GitHub.
Appendix: source
Thrown at src/components/SupplyChainPanel.ts:1180
const runResp = await runScenario({ scenarioId, iso2, disruptionPct }, { signal: runSignal });
const jobId = runResp.jobId;
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;
}View on GitHub (pinned to 7d06c8633d)