gastownhall/beads · error

target path is not a real directory: %s

Error message

target path is not a real directory: %s

What it means

inspectWorktreeTarget rejects the target if Lstat shows the path is a symlink or not a plain directory. Removal refuses to operate on symlinked or irregular paths because deleting through a symlink could damage files outside the intended worktree, and identity pinning requires a real directory.

Source

Thrown at cmd/bd/worktree_cmd.go:1009

	)
	if err != nil {
		return pinnedWorktreeTarget{}, fmt.Errorf("failed to inspect target cleanliness: %w", err)
	}

	headOID := strings.TrimSpace(string(headOutput))
	if headOID == "" || headOID != worktree.headOID {
		return pinnedWorktreeTarget{}, fmt.Errorf(
			"target HEAD disagrees with git worktree registry (registry %q, target %q)",
			worktree.headOID,
			headOID,
		)
	}
	pathInfo, err := os.Lstat(worktree.path)
	if err != nil {
		return pinnedWorktreeTarget{}, fmt.Errorf("failed to pin target directory identity: %w", err)
	}
	if pathInfo.Mode()&os.ModeSymlink != 0 || !pathInfo.IsDir() {
		return pinnedWorktreeTarget{}, fmt.Errorf("target path is not a real directory: %s", worktree.path)
	}
	gitDirInfo, err := os.Lstat(gitDir)
	if err != nil {
		return pinnedWorktreeTarget{}, fmt.Errorf("failed to pin target git directory identity: %w", err)
	}
	if gitDirInfo.Mode()&os.ModeSymlink != 0 || !gitDirInfo.IsDir() {
		return pinnedWorktreeTarget{}, fmt.Errorf("target git directory is not a real directory: %s", gitDir)
	}
	gitMarkerPath := filepath.Join(worktree.path, ".git")
	gitMarkerInfo, err := os.Lstat(gitMarkerPath)
	if err != nil {
		return pinnedWorktreeTarget{}, fmt.Errorf("failed to pin target git marker identity: %w", err)
	}
	if gitMarkerInfo.Mode()&os.ModeSymlink != 0 || !gitMarkerInfo.Mode().IsRegular() {
		return pinnedWorktreeTarget{}, fmt.Errorf("target git marker is not a regular file: %s", gitMarkerPath)
	}
	gitDirFingerprint, err := fingerprintWorktreeFilesystem(gitDir)
	if err != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Replace the symlink with a real directory: remove the link and move the target directory into place, or create the worktree fresh with `git worktree add <real-path>`.
  2. Register the real (resolved) path with bd and remove via that path.
  3. If the path is now a file, delete the file and re-add the worktree properly.
  4. Avoid symlinked worktree locations in your project layout.

Example fix

// before
ln -s /mnt/big/worktrees/feature-x /repo/.worktrees/feature-x
bd worktree remove feature-x
// error: target path is not a real directory
// after
rm /repo/.worktrees/feature-x
mv /mnt/big/worktrees/feature-x /repo/.worktrees/feature-x
bd worktree remove feature-x
Defensive patterns

Strategy: validation

Validate before calling

import { lstatSync } from "node:fs";
const st = lstatSync(worktreePath); // lstat: does not follow symlinks
if (st.isSymbolicLink() || !st.isDirectory()) {
  throw new Error("path must be a real directory, not a symlink or file");
}

Type guard

function isRealDirectory(p) {
  try { const st = lstatSync(p); return st.isDirectory() && !st.isSymbolicLink(); }
  catch { return false; }
}

Prevention

When it happens

Trigger: The registered worktree path is a symlink pointing elsewhere (someone replaced the directory with a link); the path is a regular file; a filesystem oddity after registry creation such that the path no longer Lstats as a real directory.

Common situations: Developers symlinking worktrees from another disk (e.g. ~/worktrees -> /mnt/big/worktrees); a build tool replacing the directory with a symlink; registry recording a path that later became a file; Windows junctions or macOS weirdness.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/dc1186c22fdfe015. Report an issue: GitHub.