ruvnet/ruflo · error · Error

run sourceStateId is invalid

Error message

run sourceStateId is invalid

What it means

run.sourceState.sourceStateId must match the DIGEST regex ^sha256:[0-9a-f]{64}$ — the canonical content-address form used throughout the harness ('sha256:' prefix plus exactly 64 lowercase hex characters). validateRun rejects bare hex, uppercase hex, sha512, base64, git SHA-1 object names, and truncated digests. The strict form keeps receipts content-addressable and cross-checkable.

Source

Thrown at v3/@claude-flow/codex/src/harness/in-memory-run-receipt-reference.ts:25

function sha256(value: string): string {
  return `sha256:${createHash('sha256').update(value).digest('hex')}`;
}

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.
 *

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Take sourceStateId from the repository source-state snapshot the run actually executed against
  2. If hashing manually, format exactly: `sha256:${createHash('sha256').update(value).digest('hex')}`
  3. Pre-validate with /^sha256:[0-9a-f]{64}$/ before recordRun

Example fix

// before
sourceState: { sourceStateId: createHash('sha256').update(state).digest('hex') }

// after
sourceState: { sourceStateId: `sha256:${createHash('sha256').update(state).digest('hex')}` }
Defensive patterns

Strategy: type-guard

Validate before calling

const SHA256_DIGEST = /^sha256:[0-9a-f]{64}$/;
function assertSourceStateId(id: string): void {
  if (!SHA256_DIGEST.test(id)) {
    throw new TypeError(`sourceStateId must be 'sha256:' + 64 lowercase hex chars, got: ${id}`);
  }
}

Type guard

function isCanonicalSha256(value: unknown): value is string {
  return typeof value === 'string' && /^sha256:[0-9a-f]{64}$/.test(value);
}

Try / catch

try {
  ledger.recordRun(run);
} catch (error) {
  if (error instanceof Error && error.message === 'run sourceStateId is invalid') {
    // re-derive the snapshot and retry with its identifier
    const snapshot = await captureSourceState(repoRoot);
    run.sourceState = snapshot.sourceState;
    ledger.recordRun(run);
  } else throw error;
}

Prevention

When it happens

Trigger: Setting sourceStateId to createHash('sha256').update(x).digest('hex') (missing prefix), 'SHA256:AB...' (wrong case), digest('base64'), a 40-char git commit hash, or a UUID.

Common situations: Hashing by hand instead of reusing the sourceStateId produced by the repository source-state snapshot API; copy-pasting git commit SHAs as 'source state IDs'; mixing digest formats when porting evidence from another tool.

Related errors


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