{"record":{"id":"85952282fbdb3349","repo":"Hmbown/CodeWhale","slug":"could-not-resolve-spillover-path-empty-invalid-id","errorCode":null,"errorMessage":"could not resolve spillover path (empty/invalid id or missing home directory)","messagePattern":"could not resolve spillover path \\(empty/invalid id or missing home directory\\)","errorType":"validation","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/truncate.rs","lineNumber":235,"sourceCode":"        return Ok(path);\n    }\n    if let Some(parent) = path.parent() {\n        fs::create_dir_all(parent)?;\n    }\n    crate::utils::write_atomic(&path, content.as_bytes())?;\n    Ok(path)\n}\n\n/// Write `content` to the spillover file for `id`. Creates the\n/// parent directory if needed. Returns the resolved path on success.\n///\n/// Atomic via `write` + filesystem rename guarantees from the\n/// underlying OS — the file is created at a temp name first and\n/// then renamed into place. Failures bubble up as `io::Error` so the\n/// caller can decide whether to surface them.\npub fn write_spillover(id: &str, content: &str) -> io::Result<PathBuf> {\n    let path = spillover_path(id).ok_or_else(|| {\n        io::Error::new(\n            io::ErrorKind::InvalidInput,\n            \"could not resolve spillover path (empty/invalid id or missing home directory)\",\n        )\n    })?;\n    if let Some(parent) = path.parent() {\n        fs::create_dir_all(parent)?;\n    }\n    crate::utils::write_atomic(&path, content.as_bytes())?;\n    Ok(path)\n}\n\n/// Drop spillover files older than `max_age`. Returns the number of\n/// files removed. Non-fatal: directory-missing returns 0; per-file\n/// errors are logged and skipped. Mirrors\n/// [`crate::session_manager::prune_workspace_snapshots`].\npub fn prune_older_than(max_age: Duration) -> io::Result<usize> {\n    let Some(root) = spillover_root() else {\n        return Ok(0);","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/tools/truncate.rs#L217-L253","documentation":"InvalidInput returned by write_spillover when spillover_path(id) is None. That happens for two distinct reasons: sanitise_id(id) stripped the id to nothing (empty or entirely-invalid identifier), or spillover_root() could not resolve the spillover storage root (home directory unresolvable). The id is sanitized precisely so hostile values cannot escape the storage directory; an id that sanitizes to nothing has no valid target file.","triggerScenarios":"Calling write_spillover (or a tool-output path that reaches it) with an empty id, an id made only of path/separator characters that sanitisation removes, or running in an environment where the user's home directory cannot be determined (HOME unset).","commonSituations":"Caller passes a None-defaulted or unwrapped id string; ids built from untrusted input that reduces to empty after cleaning; service/daemon contexts with HOME stripped; chroot/sandboxes without a home.","solutions":["Pass a real identifier: a tool-call id or the raw 64-char lowercase SHA-256 hex digest","Check the id before calling: non-empty and containing at least one alphanumeric character after cleaning","If the id is fine, fix the environment: ensure HOME (or the configured Codewhale home) is set for the process"],"exampleFix":"// before\nwrite_spillover(\"\", &content)?; // InvalidInput\n\n// after\nlet id = tool_call_id(); // e.g. \"call_7f3a...\" or 64-char sha hex\nassert!(!id.trim().is_empty());\nwrite_spillover(&id, &content)?;","handlingStrategy":"validation","validationCode":"// spillover_path is public: resolve first, write only when Some.\nmatch spillover_path(&id) {\n    Some(path) => { /* safe to write_spillover(&id, content) */ }\n    None => { /* fix id (non-empty, alnum) or HOME env; skip spillover otherwise */ }\n}","typeGuard":"fn is_unresolvable_spillover(e: &std::io::Error) -> bool {\n    e.kind() == std::io::ErrorKind::InvalidInput && e.to_string().contains(\"could not resolve spillover path\")\n}","tryCatchPattern":null,"preventionTips":["Always pass real identifiers (tool-call ids or 64-char lowercase sha hex)","Assert a usable HOME (or Codewhale home) at startup in service contexts","Treat spillover as optional: when the path cannot resolve, keep output inline rather than failing the tool call"],"tags":["spillover","identifier-validation","home-directory","environment","rust"],"backgroundTag":"missing-home-directory","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}