ruvnet/ruflo · error
${label} must be a canonical sha256 digest
Error message
${label} must be a canonical sha256 digest What it means
Digest fields in build evidence (build input digest, toolchain digest, and source state id) must match the canonical form enforced by the regex /^sha256:[0-9a-f]{64}$/ — the literal prefix 'sha256:' followed by exactly 64 lowercase hex characters. Uppercase hex, a missing prefix, a different algorithm (sha512, a git object id), or a wrong-length string all throw, with the failing field named in the label.
Source
Thrown at v3/@claude-flow/codex/src/harness/build-evidence.ts:60
assurance: 'declared-unsigned';
sourceStateId: string;
buildInputs: readonly DeclaredBuildInput[];
toolchains: readonly DeclaredToolchain[];
evidenceDigest: string;
}
function compare(left: string, right: string): number {
return left < right ? -1 : left > right ? 1 : 0;
}
function requireText(value: string, label: string): string {
const result = value.trim();
if (!result) throw new Error(`${label} must be non-empty`);
return result;
}
function requireDigest(value: string, label: string): string {
if (!DIGEST.test(value)) throw new Error(`${label} must be a canonical sha256 digest`);
return value;
}
function normalizePath(value: string): string {
const path = requireText(value, 'build input path');
if (
path.includes('\\')
|| path.startsWith('/')
|| path.startsWith('-')
|| path !== path.normalize('NFC')
|| path.split('/').some((part) => !part || part === '.' || part === '..')
) {
throw new Error(`unsafe build input path: ${value}`);
}
return path;
}
function sha256(value: string): string {View on GitHub (pinned to fa13ee4ad6)
Solutions
- Format every digest as `sha256:${hex}` with lowercase hex, exactly 64 characters
- Compute with node:crypto: `sha256:${createHash('sha256').update(bytes).digest('hex')}`
- When carrying evidence produced by captureBuildEvidence, reuse the returned digests verbatim instead of reformatting them
Example fix
// before
const digest = '9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08';
// after
import { createHash } from 'node:crypto';
const digest = `sha256:${createHash('sha256').update(content).digest('hex')}`; Defensive patterns
Strategy: validation
Validate before calling
const CANONICAL_SHA256 = /^sha256:[0-9a-f]{64}$/;
function isCanonicalSha256(value: string): boolean {
return CANONICAL_SHA256.test(value);
}
function toCanonicalSha256(bytes: Buffer | string): string {
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
} Type guard
function isCanonicalSha256(value: unknown): value is string {
return typeof value === 'string' && /^sha256:[0-9a-f]{64}$/.test(value);
} Prevention
- Always produce digests with the same helper that prepends 'sha256:' and lowercases hex
- Reject digests from external tools at ingest with the regex before they reach evidence code
- Never mix git object ids into digest fields — they are different namespaces
When it happens
Trigger: Passing bare 64-char hex from sha256sum without the 'sha256:' prefix; uppercase hex; a SHA-1 git SHA; truncated or double-prefixed digests ('sha256:sha256:...').
Common situations: Piping sha256sum output straight into declarations; mixing git commit/blob SHAs with content digests; reformatting evidence produced by another hasher or registry (which often use uppercase or sha512).
Related errors
- ${label} must be non-empty
- build evidence path is not a file or symlink: ${path}
- duplicate declared build input
- duplicate declared toolchain
- KV cache integrity check failed: hash mismatch
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/333772a240710f5b.
Report an issue: GitHub.