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
- Compute the hash with the library's own hashProviderFileContent(bytes) so it is a bare lowercase hex SHA-256.
- Strip any prefix/suffix from the string before passing it: keep only the 64 hex characters.
- Validate with /^[0-9a-f]{64}$/i before calling; reject or re-upload when invalid.
- 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
- Always derive hashes via hashProviderFileContent, never ad-hoc createHash calls
- Never pass provider-style identifiers like "sha256-abc..." — strip prefixes first
- Validate persisted index entries (64 hex chars) before use; drop corrupt ones
- Keep hash serialization as bare lowercase hex end to end
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
- Gemini Files API finalize response contains an invalid file.
- ${label} must be a non-empty string
- compute_fact_id: ${name} must be non-empty
- Unsupported language '{value}'. Supported: {}
- Unable to infer language from file extension: {}. Specify `l
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/eff9ff95f69da762.
Report an issue: GitHub.