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

  1. Map signal deaths to the conventional 128+signal value (137 for SIGKILL, 143 for SIGTERM) so exitCode is always an integer
  2. Default missing or non-numeric codes to a documented sentinel (e.g. 125) before recordRun
  3. 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

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


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/8218bb55458f47af. Report an issue: GitHub.