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 localView on GitHub (pinned to fa13ee4ad6)
Solutions
- Compute both with one shared helper: `const sha256 = (v: string) => \`sha256:\${createHash('sha256').update(v).digest('hex')}\``
- Derive evidenceDigest from canonicalJson(evidence) so the format matches the ledger's own convention
- 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
- Compute digests with a single shared sha256() helper returning the canonical form
- Derive evidenceDigest from canonicalJson so it matches ledger expectations byte-for-byte
- Reject uppercase or unprefixed digests in code review of evidence-building code
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
- run sourceStateId is invalid
- ${label} must be a canonical sha256 digest
- ${label} must be an ISO timestamp
- run execution, session, and workload identity are required
- run exitCode must be a safe integer
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/2a3f7ca3478f1130.
Report an issue: GitHub.