Hmbown/CodeWhale · error

operation_key cannot be empty

Error message

operation_key cannot be empty

What it means

validate_runtime_turn_operation_key enforces that an operation key is a non-empty, trimmed UTF-8 string of at most MAX_RUNTIME_TURN_OPERATION_KEY_BYTES (128) bytes. An empty string fails immediately. The key names the exclusive turn operation being claimed, so an empty key cannot address anything.

Solutions

  1. Fix the key construction so it embeds a real, non-empty turn/operation identifier.
  2. Log or inspect the composed key before the claim call to catch empty components at the source.
  3. If the caller legitimately has no id yet, generate one (e.g., uuid) instead of passing an empty string.
  4. Add an early assertion in the composing code that the key is non-empty before invoking the store.

Example fix

// before
let key = format!("turn-{}-{}", op.kind, op.id); // op.id empty -> key still invalid-ish or empty
store.claim(key)?; // "operation_key cannot be empty"

// after
let id = if op.id.is_empty() { uuid::Uuid::new_v4().to_string() } else { op.id.clone() };
let key = format!("turn-{}-{}", op.kind, id);
assert!(!key.trim().is_empty());
store.claim(key)?;
Defensive patterns

Strategy: validation

Validate before calling

fn valid_op_key(key: &str) -> bool {
    !key.is_empty()
        && key.len() <= 128
        && key.trim() == key
}
if !valid_op_key(&key) { anyhow::bail!("refusing to claim with invalid operation_key"); }

Type guard

fn non_empty_key(key: &str) -> Option<&str> {
    (!key.is_empty() && key.trim() == key).then_some(key)
}

Try / catch

match store.claim_turn_op(&key) {
    Ok(c) => c,
    Err(e) if e.to_string().contains("cannot be empty") => {
        eprintln!("operation key was empty; check key construction");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling the claim/lookup APIs with an operation key of "" — typically a key built by concatenating fields where the distinguishing part (turn id, tool name) was empty or the format string lost its substitution.

Common situations: A formatting bug composing keys like format!("{}", turn_id) with an unset turn_id; copying a key placeholder without filling it in; a tool returning an empty identifier that was used verbatim as the key.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/8bd57b72c0b4cb21. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/runtime_threads.rs:3499

struct PreparedRuntimeTurnOperation {
    binding: RuntimeTurnOperationBinding,
    requested_turn_id: Option<String>,
}

/// Lookup errors deliberately omit operation keys and persisted file paths.
#[derive(Debug, thiserror::Error)]
pub(crate) enum RuntimeTurnOperationLookupError {
    #[error("Invalid thread id or operation key")]
    InvalidRequest,
    #[error("Turn operation acceptance is incomplete; retry lookup")]
    Incomplete,
    #[error("Turn operation lookup unavailable")]
    Unavailable,
}

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(())

View on GitHub (pinned to 73e0f67d83)