{"record":{"id":"eff9ff95f69da762","repo":"can1357/oh-my-pi","slug":"provider-file-content-hash-must-be-a-lowercase-or","errorCode":null,"errorMessage":"Provider file content hash must be a lowercase or uppercase SHA-256 hex digest","messagePattern":"Provider file content hash must be a lowercase or uppercase SHA-256 hex digest","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/provider-file-types.ts","lineNumber":164,"sourceCode":"\n/** Convert a durable cache handle to the provider reference carried by AI image content. */\nexport function toProviderFileReference(handle: ProviderFileHandle): ProviderFileReference {\n\treturn {\n\t\tprovider: handle.provider,\n\t\t...(handle.id === undefined ? {} : { id: handle.id }),\n\t\t...(handle.uri === undefined ? {} : { uri: handle.uri }),\n\t\t...(handle.expiresAt === undefined ? {} : { expiresAt: handle.expiresAt }),\n\t};\n}\n\nfunction cacheKey(provider: ProviderFileProvider, credentialHash: string, contentHash: string): string {\n\treturn JSON.stringify([provider, credentialHash, contentHash]);\n}\n\nfunction normalizeContentHash(contentHash: string): string {\n\tconst normalized = contentHash.toLowerCase();\n\tif (!SHA256_HEX_PATTERN.test(normalized)) {\n\t\tthrow new Error(\"Provider file content hash must be a lowercase or uppercase SHA-256 hex digest\");\n\t}\n\treturn normalized;\n}\n\nfunction errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nfunction containsCredential(value: string, credential: string): boolean {\n\treturn credential.length > 0 && value.includes(credential);\n}\n\nfunction sanitizeDeleteAction(action: RemoteDeleteAction, credential: string): RemoteDeleteAction {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(action.url);\n\t} catch {\n\t\tif (containsCredential(action.url, credential)) {","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/provider-file-types.ts#L146-L182","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nkey(provider, cred, `sha256:${digest}`)\n// after\nimport { createHash } from \"node:crypto\";\nconst hex = createHash(\"sha256\").update(bytes).digest(\"hex\"); // bare 64-char hex\nkey(provider, cred, hex);","handlingStrategy":"validation","validationCode":"const SHA256_HEX = /^[0-9a-f]{64}$/i;\nif (typeof contentHash !== \"string\" || !SHA256_HEX.test(contentHash)) {\n  throw new Error(`contentHash must be 64-char SHA-256 hex, got: ${String(contentHash).slice(0, 20)}`);\n}","typeGuard":"function isSha256Hex(s: unknown): s is string {\n  return typeof s === \"string\" && /^[0-9a-f]{64}$/.test(s);\n}","tryCatchPattern":"try {\n  const entry = openProviderFileEntry(provider, credential, contentHash);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"SHA-256 hex digest\")) {\n    return recomputeAndUpload(bytes); // hash was corrupt — recompute via hashProviderFileContent\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["validation","hashing","sha256","schema-validation"],"backgroundTag":"invalid-hash-format","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}