ruvnet/ruflo · error · Error
run exitCode must be a safe integer
Error message
run exitCode must be a safe integer
What it means
run.exitCode must satisfy Number.isSafeInteger: integers within the 2^53-1 range, including 0. The check rejects null/undefined (typical when a process died from a signal), NaN, fractional values, and integers beyond the safe range. exitCode feeds the canonical JSON receipt digest, so its numeric representation must be exact.
Source
Thrown at v3/@claude-flow/codex/src/harness/in-memory-run-receipt-reference.ts:29
function copy<T>(value: T): T {
return structuredClone(value);
}
function timestamp(value: string, label: string): number {
const parsed = Date.parse(value);
if (!Number.isFinite(parsed)) throw new Error(`${label} must be an ISO timestamp`);
return parsed;
}
function validateRun(run: RunEvidence): void {
if (!run.executionId.trim() || !run.sessionId.trim() || !run.workloadId.trim()) {
throw new Error('run execution, session, and workload identity are required');
}
if (!DIGEST.test(run.sourceState.sourceStateId)) throw new Error('run sourceStateId is invalid');
if (!DIGEST.test(run.commandDigest) || !DIGEST.test(run.evidenceDigest)) {
throw new Error('run command and evidence digests must be canonical sha256 values');
}
if (!Number.isSafeInteger(run.exitCode)) throw new Error('run exitCode must be a safe integer');
const started = timestamp(run.startedAt, 'startedAt');
const completed = timestamp(run.completedAt, 'completedAt');
if (completed < started) throw new Error('run completedAt precedes startedAt');
if (
run.buildEvidence !== undefined
&& run.buildEvidence.sourceStateId !== run.sourceState.sourceStateId
) {
throw new Error('build evidence belongs to a different source state');
}
}
/**
* Unsigned, non-durable, content-addressed in-memory conformance ledger.
*
* Exact retries converge on one receipt. Reusing an execution ID with changed
* evidence is refused rather than rewriting history. These receipts are local
* debugging evidence and cannot authorize enforce mode or release.
*/View on GitHub (pinned to fa13ee4ad6)
Solutions
- Map signal deaths to the conventional 128+signal value (137 for SIGKILL, 143 for SIGTERM) so exitCode is always an integer
- Default missing or non-numeric codes to a documented sentinel (e.g. 125) before recordRun
- Pre-validate with Number.isSafeInteger(run.exitCode) at the call site
Example fix
// before
receipt.recordRun({ ..., exitCode: result.exitCode }); // null when killed by signal
// after
const exitCode = Number.isSafeInteger(result.exitCode) ? result.exitCode : 128 + (result.signal ? signalNumber(result.signal) : 0);
receipt.recordRun({ ..., exitCode }); Defensive patterns
Strategy: validation
Validate before calling
function toSafeExitCode(raw: number | null | undefined, signal?: string | null): number {
if (Number.isSafeInteger(raw)) return raw;
if (signal) return 128 + signalNumber(signal);
return 125; // documented sentinel for 'no integer exit code available'
} Type guard
function isSafeExitCode(value: unknown): value is number {
return typeof value === 'number' && Number.isSafeInteger(value);
} Try / catch
try {
ledger.recordRun(run);
} catch (error) {
if (error instanceof Error && error.message === 'run exitCode must be a safe integer') {
run.exitCode = isSafeExitCode(run.exitCode) ? run.exitCode : 125;
ledger.recordRun(run);
} else throw error;
} Prevention
- Normalize child-process results: map signal deaths to 128+signal before recording
- Never forward raw spawn exitCode without a null/undefined check
- Parse exit codes from text with parseInt and validate with Number.isSafeInteger
When it happens
Trigger: recordRun with exitCode: null after child.kill(); exitCode: NaN from parseInt('SIGKILL'); exitCode: 0.5 parsed from stdout text; exitCode: 1e21 or a snowflake-sized number; exitCode left undefined in a hand-built object.
Common situations: Forwarding spawn/exec result exitCode without handling signal deaths; parsing exit codes from log lines; test stubs that omit exitCode; 64-bit exit-status words from other runtimes.
Related errors
- ${label} must be an ISO timestamp
- run execution, session, and workload identity are required
- run sourceStateId is invalid
- run command and evidence digests must be canonical sha256 va
- run completedAt precedes startedAt
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/8218bb55458f47af.
Report an issue: GitHub.