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
- Slugify the id to the allowed charset before storing.
- Use a generated opaque id (UUID/ULID) instead of a human string.
- 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
- Use generated UUID/ULID ids instead of human strings
- Slugify any human-derived label before storing
- Never use emails, URLs, or paths as record ids
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
- artifact id and extension must contain safe ASCII characters
- Invalid durable task id
- must not be empty
- must not be empty
- cannot be empty
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> {
threadView on GitHub (pinned to 73e0f67d83)