Hmbown/CodeWhale · error
must be a SHA-256 hex digest
Error message
{label} must be a SHA-256 hex digest What it means
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.
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
Example fix
// before let fp = BASE64.encode(sha256(data)); // after let fp = hex::encode(sha256(data)); // 64 hex chars
Defensive patterns
Strategy: validation
Validate before calling
fn is_sha256_hex(s: &str) -> bool {
s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit())
} Type guard
fn is_sha256_fingerprint(s: &str) -> bool { s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit()) } Prevention
- Always use hex::encode for digest fingerprints
- Strip algorithm prefixes ('sha256:') before passing
- Never store fingerprints base64-encoded if the API expects hex
When it happens
Trigger: 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.
Common situations: Storing a hash base64-encoded then passing it back; computing sha1 (40 chars) instead of sha256; including a 'sha256:' prefix.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Invalid checksum manifest line
- invalid Runtime Chat fingerprint
- late usage ledger has an unsupported or unbounded shape
- workspace identity must be a lowercase SHA-256 hash
- 1
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/fbcbeffc6ca3fd22.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:3515
fn validate_runtime_turn_operation_key(value: &str) -> Result<()> {
if value.is_empty() {
bail!("operation_key cannot be empty");
}
if value.len() > MAX_RUNTIME_TURN_OPERATION_KEY_BYTES {
bail!("operation_key cannot exceed {MAX_RUNTIME_TURN_OPERATION_KEY_BYTES} UTF-8 bytes");
}
if value.trim() != value {
bail!("operation_key cannot contain leading or trailing whitespace");
}
if value.chars().any(char::is_control) {
bail!("operation_key cannot contain control characters");
}
Ok(())
}
fn validate_sha256_fingerprint(value: &str, label: &str) -> Result<()> {
if value.len() != 64 || !value.bytes().all(|byte| byte.is_ascii_hexdigit()) {
bail!("{label} must be a SHA-256 hex digest");
}
Ok(())
}
fn runtime_turn_operation_key_fingerprint(
owner_id: &str,
thread_id: &str,
operation_key: &str,
) -> Result<String> {
validate_runtime_turn_operation_key(operation_key)?;
Ok(crate::hashing::sha256_hex(format!(
"runtime-turn-operation\u{1f}{owner_id}\u{1f}{thread_id}\u{1f}{operation_key}"
)))
}
#[allow(clippy::too_many_arguments)]
fn runtime_turn_request_fingerprint(
thread: &ThreadRecord,View on GitHub (pinned to 73e0f67d83)