Hmbown/CodeWhale · error

contains unsupported characters

Error message

{label} contains unsupported characters

What it means

`validated_record_id` restricts record ids to ASCII alphanumeric characters plus `-` and `_`, because ids are used as file names under the threads store. Any other character (slash, dot, space, unicode) is rejected — this also blocks path traversal.

Solutions

  1. Slugify the id to the allowed charset before storing.
  2. Use a generated opaque id (UUID/ULID) instead of a human string.
  3. Keep a mapping from the human-readable label to a safe stored id.

Example fix

// before
let thread_id = format!("{}'s session", user.email); // contains @ and .
store.save_turn(&thread_id, &turn_id, record)?;
// after
let thread_id = uuid::Uuid::new_v4().to_string();
store.save_turn(&thread_id, &turn_id, record)?;
Defensive patterns

Strategy: validation

Validate before calling

fn id_charset_ok(id: &str) -> bool { !id.is_empty() && id.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_')) }

Type guard

fn valid_record_id(id: &str) -> Option<&str> { id.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_')).then_some(id) }

Try / catch

match store.save_turn(thread_id, turn_id, record) { Err(e) if e.contains("unsupported characters") => { let safe = slugify(turn_id); retry_with(safe) }, Err(e) => return Err(e.into()), Ok(v) => v }

Prevention

When it happens

Trigger: Calling any threads-store API (`record_path`, `save_turn`, `save_item`, list functions) with an id containing characters outside `[A-Za-z0-9_-]`.

Common situations: Using an email, URL, or file path as a thread id; ids built from user names with spaces or dots; malicious `../` input being safely rejected.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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

Appendix: source

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

            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}"))
        .collect::<String>();
    Ok(format!("ws_{digest}"))
}

fn agent_mail_sender_identity(thread: &ThreadRecord) -> Result<String> {
    thread

View on GitHub (pinned to 73e0f67d83)