Hmbown/CodeWhale · error · anyhow::Error

{source} is not a directory: {}. Resume/fork from an existin

Error message

{source} is not a directory: {}. Resume/fork from an existing workspace or pass an explicit `working_dir`/`cwd`

What it means

Before executing a shell command, the resolved working directory is stat'ed. A preceding check already handles the missing/unavailable path with a distinct message; this error fires when metadata exists but is not a directory (a regular file, socket, device, etc.). The message distinguishes whether the path came from a saved session workspace (inherited on resume/fork) or from an explicitly requested working directory, and names the offending path.

Source

Thrown at crates/tui/src/tools/shell.rs:142

fn validate_shell_working_dir(path: &Path, inherited_session_workspace: bool) -> Result<()> {
    let metadata = std::fs::metadata(path).with_context(|| {
        let source = if inherited_session_workspace {
            "saved session workspace"
        } else {
            "requested working directory"
        };
        format!(
            "{source} is unavailable: {}. Restore or remap that directory, resume/fork the session from an existing workspace, or pass an explicit `working_dir`/`cwd` to exec_shell",
            path.display()
        )
    })?;
    if !metadata.is_dir() {
        let source = if inherited_session_workspace {
            "saved session workspace"
        } else {
            "requested working directory"
        };
        return Err(anyhow!(
            "{source} is not a directory: {}. Resume/fork from an existing workspace or pass an explicit `working_dir`/`cwd`",
            path.display()
        ));
    }
    Ok(())
}

/// Status of a shell process.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ShellStatus {
    Running,
    Completed,
    Failed,
    Killed,
    TimedOut,
}

/// Result from a shell command execution.

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Pass an explicit working_dir/cwd that is an existing directory.
  2. If resuming/forking, resume from a session whose workspace directory actually exists, or remap the workspace.
  3. Check the path with ls/stat to confirm it is a directory, and fix the entry (remove the blocking file or correct the symlink).

Example fix

# before
exec_shell("cargo build", working_dir="./run.sh")

# after
exec_shell("cargo build", working_dir="./")
Defensive patterns

Strategy: validation

Validate before calling

let dir: PathBuf = resolve_working_dir(requested, session)?;
if !dir.is_dir() {
    anyhow::bail!("working directory {} is not a directory", dir.display());
}
exec_shell_with_dir(command, &dir)?;

Try / catch

match exec_shell(cmd, Some(dir), None).await {
    Ok(out) => out,
    Err(e) if e.to_string().contains("is not a directory") => {
        let fallback = current_workspace_root();
        exec_shell(cmd, Some(&fallback), None).await?
    }
    Err(e) => return Err(e),
};

Prevention

When it happens

Trigger: Calling exec_shell with working_dir/cwd pointing at a file; or resuming/forking a session whose saved workspace path now exists but is not a directory (e.g. a file was created at that path).

Common situations: A symlink in the workspace path resolves to a file; the user passes a script path instead of its directory as cwd; on resume the old workspace was replaced by a file.

Related errors


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