{"record":{"id":"fbcbeffc6ca3fd22","repo":"Hmbown/CodeWhale","slug":"label-must-be-a-sha-256-hex-digest","errorCode":null,"errorMessage":"{label} must be a SHA-256 hex digest","messagePattern":"(.+?) must be a SHA-256 hex digest","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":3515,"sourceCode":"fn validate_runtime_turn_operation_key(value: &str) -> Result<()> {\n    if value.is_empty() {\n        bail!(\"operation_key cannot be empty\");\n    }\n    if value.len() > MAX_RUNTIME_TURN_OPERATION_KEY_BYTES {\n        bail!(\"operation_key cannot exceed {MAX_RUNTIME_TURN_OPERATION_KEY_BYTES} UTF-8 bytes\");\n    }\n    if value.trim() != value {\n        bail!(\"operation_key cannot contain leading or trailing whitespace\");\n    }\n    if value.chars().any(char::is_control) {\n        bail!(\"operation_key cannot contain control characters\");\n    }\n    Ok(())\n}\n\nfn validate_sha256_fingerprint(value: &str, label: &str) -> Result<()> {\n    if value.len() != 64 || !value.bytes().all(|byte| byte.is_ascii_hexdigit()) {\n        bail!(\"{label} must be a SHA-256 hex digest\");\n    }\n    Ok(())\n}\n\nfn runtime_turn_operation_key_fingerprint(\n    owner_id: &str,\n    thread_id: &str,\n    operation_key: &str,\n) -> Result<String> {\n    validate_runtime_turn_operation_key(operation_key)?;\n    Ok(crate::hashing::sha256_hex(format!(\n        \"runtime-turn-operation\\u{1f}{owner_id}\\u{1f}{thread_id}\\u{1f}{operation_key}\"\n    )))\n}\n\n#[allow(clippy::too_many_arguments)]\nfn runtime_turn_request_fingerprint(\n    thread: &ThreadRecord,","sourceCodeStart":3497,"sourceCodeEnd":3533,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L3497-L3533","documentation":"validate_sha256_fingerprint requires a value to be exactly 64 ASCII hex digits, i.e. a SHA-256 digest. The runtime compares fingerprints (e.g. runtime_turn_operation_key_fingerprint) to detect duplicate/committed operations, so a malformed digest would silently defeat that comparison.","triggerScenarios":"Calling an API that takes a fingerprint/label argument with a string that is not 64 hex chars — truncated digests, base64-encoded hashes, uppercase non-hex text, or plain IDs.","commonSituations":"Storing a hash base64-encoded then passing it back; computing sha1 (40 chars) instead of sha256; including a 'sha256:' prefix.","solutions":["Ensure the value is the lowercase/uppercase hex output of SHA-256 (exactly 64 chars, [0-9a-fA-F])","Strip any 'sha256:' prefix before passing","Re-encode base64 digests to hex before calling"],"exampleFix":"// before\nlet fp = BASE64.encode(sha256(data));\n// after\nlet fp = hex::encode(sha256(data)); // 64 hex chars","handlingStrategy":"validation","validationCode":"fn is_sha256_hex(s: &str) -> bool {\n    s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit())\n}","typeGuard":"fn is_sha256_fingerprint(s: &str) -> bool { s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit()) }","tryCatchPattern":null,"preventionTips":["Always use hex::encode for digest fingerprints","Strip algorithm prefixes ('sha256:') before passing","Never store fingerprints base64-encoded if the API expects hex"],"tags":["validation","hash","sha256"],"backgroundTag":"invalid-argument-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}