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
- Fix the key construction so it embeds a real, non-empty turn/operation identifier.
- Log or inspect the composed key before the claim call to catch empty components at the source.
- If the caller legitimately has no id yet, generate one (e.g., uuid) instead of passing an empty string.
- 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
- Build operation keys with a single helper that asserts non-empty components.
- Generate identifiers (uuid) instead of propagating empty ids into keys.
- Log composed keys at debug level to catch empty substitutions early.
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
- --attempts must be an integer from 1 to 20
- --channel, --version and --reason are required
- Choose one Runtime --thread ID.
- click supports left with 1-3 clicks, right x1 or middle x1
- Command is required
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)