{"record":{"id":"c4d1aab986e6e784","repo":"Hmbown/CodeWhale","slug":"label-cannot-contain-leading-or-trailing-whitesp","errorCode":null,"errorMessage":"{label} cannot contain leading or trailing whitespace","messagePattern":"(.+?) cannot contain leading or trailing whitespace","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":300,"sourceCode":"    if let Some(end) = end {\n        let tail = base[end..].trim_start();\n        if !tail.is_empty() {\n            if !out.is_empty() {\n                out.push_str(\"\\n\\n\");\n            }\n            out.push_str(tail);\n        }\n    }\n    out\n}\n\nfn validated_record_id<'a>(id: &'a str, label: &str) -> Result<&'a str> {\n    let trimmed = id.trim();\n    if trimmed.is_empty() {\n        bail!(\"{label} cannot be empty\");\n    }\n    if trimmed != id {\n        bail!(\"{label} cannot contain leading or trailing whitespace\");\n    }\n    if !trimmed\n        .chars()\n        .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')\n    {\n        bail!(\"{label} contains unsupported characters\");\n    }\n    Ok(trimmed)\n}\n\nfn agent_mail_workspace_id(workspace: &Path) -> Result<String> {\n    let canonical = workspace\n        .canonicalize()\n        .with_context(|| format!(\"resolve Agent Mail workspace {}\", workspace.display()))?;\n    let digest = Sha256::digest(canonical.to_string_lossy().as_bytes());\n    let digest = digest\n        .iter()\n        .map(|byte| format!(\"{byte:02x}\"))","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L282-L318","documentation":"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.","triggerScenarios":"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`.","commonSituations":"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.","solutions":["Call `.trim()` on the id before passing it to the store API.","Fix the parsing/splitting code so whitespace is not captured in the id.","Validate/normalize ids at ingestion time (when records are first created)."],"exampleFix":"// before\nstore.list_turns_for_thread(&user_supplied_thread_id)?;\n// after\nstore.list_turns_for_thread(user_supplied_thread_id.trim())?;","handlingStrategy":"validation","validationCode":"fn clean_id(id: &str) -> Result<&str, String> { let t = id.trim(); if t != id { Err(\"id has surrounding whitespace\".into()) } else { Ok(t) } }","typeGuard":"fn is_trimmed(id: &str) -> bool { id.trim() == id }","tryCatchPattern":"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) }","preventionTips":["Trim any id parsed from user input, CLI, or files","Avoid capturing separators when splitting strings into ids","Normalize ids once at the ingestion boundary"],"tags":["validation","whitespace","storage"],"backgroundTag":"invalid-argument-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}