Hmbown/CodeWhale · error
{label} contains unsupported characters
Error message
{label} contains unsupported characters What it means
validated_record_id only accepts ASCII alphanumerics plus '-' and '_'; any other character bails with '<label> contains unsupported characters'. The allowlist exists because ids become filenames in the on-disk thread store and must be portable and injection-safe across platforms.
Source
Thrown at crates/tui/src/runtime_threads.rs:274
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 0c42157ee5)
Solutions
- Use generated opaque ids (UUID/hex) for thread, turn, and item ids and keep human names in a separate display field.
- If you must derive ids from text, slugify first: lowercase, strip to [a-z0-9_-], and append a random suffix to avoid collisions.
- Replace clock-format separators, e.g. use 20260820T123000 not 12:30:00.
Example fix
// before
let thread_id = format!("session {}", title); // may contain spaces/colons
store.load_thread(&thread_id)?; // bails: unsupported characters
// after
let thread_id = uuid::Uuid::new_v4().simple().to_string(); // [0-9a-f]{32}
store.load_thread(&thread_id)?; Defensive patterns
Strategy: validation
Validate before calling
// Rust: slugify free-form text into a store-safe id
fn slug_id(text: &str) -> String {
let slug: String = text
.to_ascii_lowercase()
.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
.collect();
let slug = slug.trim_matches('-').to_string();
format!("{}-{}", slug, uuid::Uuid::new_v4().simple())
}
let thread_id = slug_id(&title); // only [a-z0-9_-]
store.load_thread(&thread_id)?; Prevention
- Keep opaque generated ids for storage and human names for display only.
- Forbid spaces, colons, dots, slashes, and non-ASCII in id fields at input validation.
- Use timestamp formats without colons (20260820T123000) when dates appear in ids.
When it happens
Trigger: Passing ids containing spaces, dots, slashes, colons, unicode, or any symbol outside [A-Za-z0-9_-] to the thread store APIs; generating ids from free-form text such as session titles.
Common situations: Using human-readable names ('My Session: draft #2') as ids; ids derived from timestamps with colons (12:30:00); unicode identifiers from internationalized input; separators like '.' or '/' copied from other systems' id formats.
Related errors
- {label} cannot be empty
- {label} cannot contain leading or trailing whitespace
- Invalid preallocated task id: expected task_<16hex>
- ${name} is unavailable in Workflow scripts: runs must be det
- context_window must be greater than 0
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/cb7be02eb49083ae.
Report an issue: GitHub.