{"record":{"id":"a192c843412f73b0","repo":"Hmbown/CodeWhale","slug":"label-cannot-be-empty","errorCode":null,"errorMessage":"{label} cannot be empty","messagePattern":"(.+?) cannot be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":265,"sourceCode":"        .find(COMPACTION_SUMMARY_END)\n        .map(|rel| start + rel + COMPACTION_SUMMARY_END.len());\n    let mut out = base[..start].trim_end().to_string();\n    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());","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/runtime_threads.rs#L247-L283","documentation":"The runtime thread store validates every record id (thread ids, turn ids, item ids, owner ids) through validated_record_id. An id that trims to the empty string fails with '<label> cannot be empty'. Ids become filesystem path components, so blank ids are rejected before any path is built.","triggerScenarios":"Calling store APIs such as load_thread(\"\"), list_turns_for_thread(\" \"), or persisting a record whose thread_id/turn_id/owner_id is empty or whitespace-only. The label in the message tells you which id failed (e.g. 'thread id cannot be empty').","commonSituations":"Callers derive ids from user input or generated names that can be blank; a template or default produced an empty string; whitespace-only ids slip in from trimmed logs or copy-paste.","solutions":["Generate ids with the store's own id generator (UUID-style) instead of deriving them from free-form input.","Validate ids at the call site before invoking the store API (non-empty after trim).","If the id comes from user input, reject blank submissions at the UI layer with a clear message."],"exampleFix":"// before\nstore.load_thread(\"\")?;   // bails: thread id cannot be empty\n\n// after\nlet id = require_nonempty(user_input)?;\nstore.load_thread(&id)?;","handlingStrategy":"validation","validationCode":"// Rust: mirror the store's rule before calling the API\nfn is_valid_record_id(id: &str) -> bool {\n    !id.trim().is_empty()\n        && id.trim() == id\n        && id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')\n}\n\nanyhow::ensure!(is_valid_record_id(thread_id), \"invalid thread id {thread_id:?}\");\nstore.load_thread(thread_id)?;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Generate ids with UUID/hex generators instead of deriving them from user text.","Centralize id validation in one helper used by every call site.","Reject blank ids at the UI/input boundary with a descriptive message."],"tags":["validation","identifier","thread-store","rust"],"backgroundTag":"empty-identifier","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}