ruvnet/ruflo · error · Error

run command and evidence digests must be canonical sha256 va

Error message

run command and evidence digests must be canonical sha256 values

What it means

Both run.commandDigest and run.evidenceDigest must match ^sha256:[0-9a-f]{64}$ (the same DIGEST regex as sourceStateId). validateRun checks the two fields in one condition and throws this single message when either fails. The digests anchor the receipt's content address, so non-canonical forms (no prefix, wrong case, wrong algorithm) are refused.

Source

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

}

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

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Compute both with one shared helper: `const sha256 = (v: string) => \`sha256:\${createHash('sha256').update(v).digest('hex')}\``
  2. Derive evidenceDigest from canonicalJson(evidence) so the format matches the ledger's own convention
  3. Pre-validate both fields against /^sha256:[0-9a-f]{64}$/ before recordRun

Example fix

// before
{ commandDigest: hashHex(cmd), evidenceDigest: hashHexB64(evidence) }

// after
const sha256 = (v: string) => `sha256:${createHash('sha256').update(v).digest('hex')}`;
{ commandDigest: sha256(cmd), evidenceDigest: sha256(canonicalJson(evidence)) }
Defensive patterns

Strategy: type-guard

Validate before calling

const SHA256_DIGEST = /^sha256:[0-9a-f]{64}$/;
function assertDigests(run: { commandDigest: string; evidenceDigest: string }): void {
  for (const [label, value] of [['commandDigest', run.commandDigest], ['evidenceDigest', run.evidenceDigest]] as const) {
    if (!SHA256_DIGEST.test(value)) throw new TypeError(`${label} is not canonical sha256: ${value}`);
  }
}

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 command and evidence digests must be canonical sha256 values') {
    run.commandDigest = sha256(run.command);
    run.evidenceDigest = sha256(canonicalJson(run.evidence));
    ledger.recordRun(run);
  } else throw error;
}

Prevention

When it happens

Trigger: recordRun with commandDigest: sha256 hex without the 'sha256:' prefix, an uppercase-hex digest, a base64 digest, a SHA-1/SHA-512 value, or the two fields accidentally swapped with non-digest content.

Common situations: Producing digests with a different utility that returns bare or base64 hex; migrating from a tool that uses 'sha1:' style prefixes; unit tests stubbing digests as 'abc123'.

Related errors


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