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

  1. Ensure the value is the lowercase/uppercase hex output of SHA-256 (exactly 64 chars, [0-9a-fA-F])
  2. Strip any 'sha256:' prefix before passing
  3. 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

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


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)