gitbutlerapp/gitbutler · error · anyhow::Error

Path is not a directory: {path}

Error message

Path is not a directory: {path}

What it means

Windows arm of open_in_terminal (crates/but-api/src/open/mod.rs:538-540): the path exists but is not a directory. Terminals are launched with the path as working directory (or via `wt -d`), so a file path is rejected before canonicalization.

Source

Thrown at crates/but-api/src/open/mod.rs:539

        fn create_new_console(cmd: &mut Command) -> &mut Command {
            use std::os::windows::process::CommandExt;
            // CREATE_NEW_CONSOLE: Creates a new console for the process (0x00000010)
            // This allows the terminal to run independently without blocking our thread
            const CREATE_NEW_CONSOLE: u32 = 0x00000010;
            cmd.creation_flags(CREATE_NEW_CONSOLE)
        }
        #[cfg(not(windows))]
        fn create_new_console(cmd: &mut Command) -> &mut Command {
            cmd
        }

        // Validate path exists and canonicalize it to proper Windows format
        let path_buf = Path::new(&path);
        if !path_buf.exists() {
            bail!("Path does not exist: {path}");
        }
        if !path_buf.is_dir() {
            bail!("Path is not a directory: {path}");
        }

        // Canonicalize to get the absolute, properly formatted Windows path
        // This converts forward slashes to backslashes and resolves . and ..
        let canonical_path = gix::path::realpath(path_buf)
            .with_context(|| format!("Failed to canonicalize path: {path}"))?
            .to_str()
            .context("BUG: input path is String, should be able to convert back to it")?
            .to_owned();
        let canonical_path = &canonical_path;

        // Check if the terminal binary exists in PATH before attempting to launch.
        let binary_found = which::which(&terminal_id).is_ok();
        if !binary_found {
            return Err(anyhow::anyhow!("'{terminal_id}' was not found.")
                .context(but_error::Code::DefaultTerminalNotFound));
        }

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Pass the repository's directory (the project worktree root), not a file within it
  2. Derive the directory with path.dirname() when starting from a file path
  3. For revealing files rather than opening a terminal, use the reveal-in-file-manager action instead

Example fix

// before
await client.openInTerminal('wt', 'C:\repo\src\main.rs'); // Path is not a directory

// after
import { dirname } from 'path';
await client.openInTerminal('wt', dirname('C:\repo\src\main.rs'));
Defensive patterns

Strategy: validation

Validate before calling

import { stat } from 'fs/promises';

const info = await stat(targetPath).catch(() => null);
if (!info) throw new Error(`Path does not exist: ${targetPath}`);
if (!info.isDirectory()) {
  targetPath = dirname(targetPath); // terminals need a directory
}
await client.openInTerminal(terminalId, targetPath);

Type guard

async function isDirectory(p: string): Promise<boolean> {
  try {
    return (await stat(p)).isDirectory();
  } catch {
    return false;
  }
}

Try / catch

try {
  await client.openInTerminal(terminalId, path);
} catch (e) {
  if (String(e).startsWith('Path is not a directory')) {
    await client.openInTerminal(terminalId, dirname(path));
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a file inside the repo (e.g. a source file or .sln) instead of the repository directory; a path that resolves to a file because a trailing segment was truncated or appended.

Common situations: Frontend handing the selected file's path to the terminal action; scripts deriving the path from a file URI without dirname; junction/symlink pointing at a file.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/637a53ec82434892. Report an issue: GitHub.