Hmbown/CodeWhale · error

{label} cannot be empty

Error message

{label} cannot be empty

What it means

The runtime thread store validates every record id (thread ids, turn ids, item ids, owner ids) through validated_record_id. An id that trims to the empty string fails with '<label> cannot be empty'. Ids become filesystem path components, so blank ids are rejected before any path is built.

Source

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

        .find(COMPACTION_SUMMARY_END)
        .map(|rel| start + rel + COMPACTION_SUMMARY_END.len());
    let mut out = base[..start].trim_end().to_string();
    if let Some(end) = end {
        let tail = base[end..].trim_start();
        if !tail.is_empty() {
            if !out.is_empty() {
                out.push_str("\n\n");
            }
            out.push_str(tail);
        }
    }
    out
}

fn validated_record_id<'a>(id: &'a str, label: &str) -> Result<&'a str> {
    let trimmed = id.trim();
    if trimmed.is_empty() {
        bail!("{label} cannot be empty");
    }
    if trimmed != id {
        bail!("{label} cannot contain leading or trailing whitespace");
    }
    if !trimmed
        .chars()
        .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
    {
        bail!("{label} contains unsupported characters");
    }
    Ok(trimmed)
}

fn agent_mail_workspace_id(workspace: &Path) -> Result<String> {
    let canonical = workspace
        .canonicalize()
        .with_context(|| format!("resolve Agent Mail workspace {}", workspace.display()))?;
    let digest = Sha256::digest(canonical.to_string_lossy().as_bytes());

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Generate ids with the store's own id generator (UUID-style) instead of deriving them from free-form input.
  2. Validate ids at the call site before invoking the store API (non-empty after trim).
  3. If the id comes from user input, reject blank submissions at the UI layer with a clear message.

Example fix

// before
store.load_thread("")?;   // bails: thread id cannot be empty

// after
let id = require_nonempty(user_input)?;
store.load_thread(&id)?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust: mirror the store's rule before calling the API
fn is_valid_record_id(id: &str) -> bool {
    !id.trim().is_empty()
        && id.trim() == id
        && id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
}

anyhow::ensure!(is_valid_record_id(thread_id), "invalid thread id {thread_id:?}");
store.load_thread(thread_id)?;

Prevention

When it happens

Trigger: Calling store APIs such as load_thread(""), list_turns_for_thread(" "), or persisting a record whose thread_id/turn_id/owner_id is empty or whitespace-only. The label in the message tells you which id failed (e.g. 'thread id cannot be empty').

Common situations: Callers derive ids from user input or generated names that can be blank; a template or default produced an empty string; whitespace-only ids slip in from trimmed logs or copy-paste.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/a192c843412f73b0. Report an issue: GitHub.