Hmbown/CodeWhale · error · std::io::Error

could not resolve spillover path (empty/invalid id or missin

Error message

could not resolve spillover path (empty/invalid id or missing home directory)

What it means

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.

Source

Thrown at crates/tui/src/tools/truncate.rs:235

        return Ok(path);
    }
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    crate::utils::write_atomic(&path, content.as_bytes())?;
    Ok(path)
}

/// Write `content` to the spillover file for `id`. Creates the
/// parent directory if needed. Returns the resolved path on success.
///
/// Atomic via `write` + filesystem rename guarantees from the
/// underlying OS — the file is created at a temp name first and
/// then renamed into place. Failures bubble up as `io::Error` so the
/// caller can decide whether to surface them.
pub fn write_spillover(id: &str, content: &str) -> io::Result<PathBuf> {
    let path = spillover_path(id).ok_or_else(|| {
        io::Error::new(
            io::ErrorKind::InvalidInput,
            "could not resolve spillover path (empty/invalid id or missing home directory)",
        )
    })?;
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    crate::utils::write_atomic(&path, content.as_bytes())?;
    Ok(path)
}

/// Drop spillover files older than `max_age`. Returns the number of
/// files removed. Non-fatal: directory-missing returns 0; per-file
/// errors are logged and skipped. Mirrors
/// [`crate::session_manager::prune_workspace_snapshots`].
pub fn prune_older_than(max_age: Duration) -> io::Result<usize> {
    let Some(root) = spillover_root() else {
        return Ok(0);

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Pass a real identifier: a tool-call id or the raw 64-char lowercase SHA-256 hex digest
  2. Check the id before calling: non-empty and containing at least one alphanumeric character after cleaning
  3. If the id is fine, fix the environment: ensure HOME (or the configured Codewhale home) is set for the process

Example fix

// before
write_spillover("", &content)?; // InvalidInput

// after
let id = tool_call_id(); // e.g. "call_7f3a..." or 64-char sha hex
assert!(!id.trim().is_empty());
write_spillover(&id, &content)?;
Defensive patterns

Strategy: validation

Validate before calling

// spillover_path is public: resolve first, write only when Some.
match spillover_path(&id) {
    Some(path) => { /* safe to write_spillover(&id, content) */ }
    None => { /* fix id (non-empty, alnum) or HOME env; skip spillover otherwise */ }
}

Type guard

fn is_unresolvable_spillover(e: &std::io::Error) -> bool {
    e.kind() == std::io::ErrorKind::InvalidInput && e.to_string().contains("could not resolve spillover path")
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/85952282fbdb3349. Report an issue: GitHub.