koala73/worldmonitor · error · ApiError
Invalid simulation run ID
Error message
Invalid simulation run ID
What it means
getSimulationOutcome validates an optional req.runId: if supplied non-empty it must be a trimmed string of at most 128 characters passing validateRunId(). Otherwise ApiError(400, 'Invalid simulation run ID') is thrown before any cache/queue lookup.
Solutions
- Echo back the exact runId string received from the run-submission response, unmodified
- Validate client-side: typeof id === 'string' && id === id.trim() && id.length <= 128 plus the runId format regex
- If you do not have a specific run, omit runId to fall through to the :latest read path
Example fix
// before
await getSimulationOutcome(ctx, { runId: ' ' + job.id });
// after
await getSimulationOutcome(ctx, { runId: job.id.trim() }); Defensive patterns
Strategy: validation
Validate before calling
if (runId !== undefined && runId !== '') {
const ok = typeof runId === 'string' && runId.length <= 128 && runId === runId.trim() && /^[A-Za-z0-9_-]+$/.test(runId);
if (!ok) throw new Error('invalid simulation runId (trim, <=128 chars, expected format)');
} Type guard
const isRunId = (v: unknown): v is string => typeof v === 'string' && v.length > 0 && v.length <= 128 && v === v.trim() && validateRunId(v);
Try / catch
try {
return await getSimulationOutcome(ctx, { runId });
} catch (e) {
if (e instanceof ApiError && e.status === 400 && e.message === 'Invalid simulation run ID') {
return await getSimulationOutcome(ctx, {}); // fall back to :latest
}
throw e;
} Prevention
- Echo runId strings verbatim from the submission response
- Never pad or truncate stored run IDs
- Fall back to the latest endpoint when runId is unavailable
When it happens
Trigger: runId longer than 128 chars; runId with leading/trailing whitespace (' abc123 '); runId failing validateRunId's format (e.g. wrong characters or shape); runId that is not a string at all.
Common situations: Truncation/concatenation bugs building the run ID from job metadata; storing run IDs with padded whitespace in a DB column; passing an object or number ID where a string is expected.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- COMPANY_MONITORING_${field}_INVALID
- invalid ${kind} logical ID
- Unknown chokepoint ID: ${invalidCp}
- ${label} HTTP 400
- Could not resolve ${JSON.stringify(echoCountryInput(raw))} t
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/06c93df05e232e00.
Report an issue: GitHub.
Appendix: source
Thrown at server/worldmonitor/forecast/v1/get-simulation-outcome.ts:98
error: '',
theaterSummariesJson,
processing: false,
eligibleTheaterCount: typeof pointer.eligibleTheaterCount === 'number' ? pointer.eligibleTheaterCount : pointer.theaterCount,
failedTheaterCount: typeof pointer.failedTheaterCount === 'number' ? pointer.failedTheaterCount : 0,
allTheatersFailed: pointer.allTheatersFailed === true,
completionStatus: typeof pointer.completionStatus === 'string' ? pointer.completionStatus : '',
};
}
export const getSimulationOutcome: ForecastServiceHandler['getSimulationOutcome'] = async (
ctx: ServerContext,
req: GetSimulationOutcomeRequest,
): Promise<GetSimulationOutcomeResponse> => {
if (req.runId !== undefined && req.runId !== '' && (
typeof req.runId !== 'string' || req.runId.length > 128
|| req.runId !== req.runId.trim() || !validateRunId(req.runId)
)) {
throw new ApiError(400, 'Invalid simulation run ID', '');
}
// Read path when caller supplied a specific runId:
// 1. By-run hit (real outcome) → return it.
// 2. By-run hit (tombstone payload) → fall through with the tombstone note text.
// 3. By-run miss + runId in queue → return processing=true.
// 4. By-run miss + runId not queued → fall through to :latest.
// See #3734 U6.
if (req.runId) {
let byRunRaw: unknown = null;
try {
byRunRaw = await getRawJson(`${SIMULATION_OUTCOME_BY_RUN_KEY_PREFIX}:${req.runId}`);
} catch (err) {
console.warn(`[getSimulationOutcome] by-run lookup failed for ${req.runId}: ${err instanceof Error ? err.message : String(err)}`);
// Fall through to :latest below.
}
if (isOutcomePointer(byRunRaw)) {
return outcomeToResponse(byRunRaw, '');
}View on GitHub (pinned to 7d06c8633d)