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

  1. Format every digest as `sha256:${hex}` with lowercase hex, exactly 64 characters
  2. Compute with node:crypto: `sha256:${createHash('sha256').update(bytes).digest('hex')}`
  3. 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

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


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