can1357/oh-my-pi · error

Provider file content hash must be a lowercase or uppercase

Error message

Provider file content hash must be a lowercase or uppercase SHA-256 hex digest

What it means

normalizeContentHash validates that a provider file content hash is a SHA-256 hex digest (64 hex characters), lowercasing first. This error is thrown when a contentHash string passed into provider-file keying/normalization is not valid SHA-256 hex — typically a truncated, prefixed, base64, or corrupted digest read back from a cache or supplied by a caller.

Source

Thrown at packages/coding-agent/src/blob-broker/provider-file-types.ts:164

/** Convert a durable cache handle to the provider reference carried by AI image content. */
export function toProviderFileReference(handle: ProviderFileHandle): ProviderFileReference {
	return {
		provider: handle.provider,
		...(handle.id === undefined ? {} : { id: handle.id }),
		...(handle.uri === undefined ? {} : { uri: handle.uri }),
		...(handle.expiresAt === undefined ? {} : { expiresAt: handle.expiresAt }),
	};
}

function cacheKey(provider: ProviderFileProvider, credentialHash: string, contentHash: string): string {
	return JSON.stringify([provider, credentialHash, contentHash]);
}

function normalizeContentHash(contentHash: string): string {
	const normalized = contentHash.toLowerCase();
	if (!SHA256_HEX_PATTERN.test(normalized)) {
		throw new Error("Provider file content hash must be a lowercase or uppercase SHA-256 hex digest");
	}
	return normalized;
}

function errorMessage(error: unknown): string {
	return error instanceof Error ? error.message : String(error);
}

function containsCredential(value: string, credential: string): boolean {
	return credential.length > 0 && value.includes(credential);
}

function sanitizeDeleteAction(action: RemoteDeleteAction, credential: string): RemoteDeleteAction {
	let url: URL;
	try {
		url = new URL(action.url);
	} catch {
		if (containsCredential(action.url, credential)) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Compute the hash with the library's own hashProviderFileContent(bytes) so it is a bare lowercase hex SHA-256.
  2. Strip any prefix/suffix from the string before passing it: keep only the 64 hex characters.
  3. Validate with /^[0-9a-f]{64}$/i before calling; reject or re-upload when invalid.
  4. If a persisted index entry is corrupt, delete it so the file is re-uploaded and re-hashed.

Example fix

// before
key(provider, cred, `sha256:${digest}`)
// after
import { createHash } from "node:crypto";
const hex = createHash("sha256").update(bytes).digest("hex"); // bare 64-char hex
key(provider, cred, hex);
Defensive patterns

Strategy: validation

Validate before calling

const SHA256_HEX = /^[0-9a-f]{64}$/i;
if (typeof contentHash !== "string" || !SHA256_HEX.test(contentHash)) {
  throw new Error(`contentHash must be 64-char SHA-256 hex, got: ${String(contentHash).slice(0, 20)}`);
}

Type guard

function isSha256Hex(s: unknown): s is string {
  return typeof s === "string" && /^[0-9a-f]{64}$/.test(s);
}

Try / catch

try {
  const entry = openProviderFileEntry(provider, credential, contentHash);
} catch (err) {
  if (err instanceof Error && err.message.includes("SHA-256 hex digest")) {
    return recomputeAndUpload(bytes); // hash was corrupt — recompute via hashProviderFileContent
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling normalizeContentHash (via cache key construction or normalizedContentHash) with a hash that fails SHA256_HEX_PATTERN: e.g. "abc123" (truncated), "sha256:abcd..." (prefixed), base64 digest, or an empty/whitespace string from a corrupted persisted index entry.

Common situations: A persisted provider-file index was hand-edited or truncated on disk; code computed a digest with a different algorithm (md5/sha1) or in base64; a hash string includes a "sha256-" prefix copied from a provider's content identifier.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/eff9ff95f69da762. Report an issue: GitHub.