Hmbown/CodeWhale · error

cannot contain leading or trailing whitespace

Error message

{label} cannot contain leading or trailing whitespace

What it means

After checking emptiness, `validated_record_id` also rejects ids with leading or trailing whitespace. Since ids become file names, whitespace would create paths that differ subtly from the logical id, so the trimmed and original values must match exactly.

Solutions

  1. Call `.trim()` on the id before passing it to the store API.
  2. Fix the parsing/splitting code so whitespace is not captured in the id.
  3. Validate/normalize ids at ingestion time (when records are first created).

Example fix

// before
store.list_turns_for_thread(&user_supplied_thread_id)?;
// after
store.list_turns_for_thread(user_supplied_thread_id.trim())?;
Defensive patterns

Strategy: validation

Validate before calling

fn clean_id(id: &str) -> Result<&str, String> { let t = id.trim(); if t != id { Err("id has surrounding whitespace".into()) } else { Ok(t) } }

Type guard

fn is_trimmed(id: &str) -> bool { id.trim() == id }

Try / catch

match store.list_turns_for_thread(id) { Err(e) if e.contains("whitespace") => store.list_turns_for_thread(id.trim()), Err(e) => Err(e.into()), Ok(v) => Ok(v) }

Prevention

When it happens

Trigger: Passing a thread/turn/item id with surrounding spaces or newlines to any of `record_path`, `save_turn`, `save_item`, `list_turns_for_thread`, `list_items_for_turn`, `list_items_for_turns_map`.

Common situations: Ids parsed from user input, CLI args, or pasted values that carry a trailing space or newline; splitting a string that left a separator behind.

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/c4d1aab986e6e784. Report an issue: GitHub.

Appendix: source

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

    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());
    let digest = digest
        .iter()
        .map(|byte| format!("{byte:02x}"))

View on GitHub (pinned to 73e0f67d83)