twpayne/chezmoi · error

%s: not a directory

Error message

%s: not a directory

What it means

When removeTargetEntry processes each target path it expects targetAbsPath to be a directory (it first Stats it and removes it as a directory). If the stat succeeds but the path is a regular file or symlink, the operation aborts with this error because removing requires the path to be a directory entry in the source state.

Source

Thrown at internal/chezmoi/sourcestate.go:967

) error {
	// Remove empty directories with the remove_ attribute. This assumes that
	// targetRelPaths is already sorted and iterates in reverse order so that
	// children are removed before their parents.
TARGET:
	for _, targetRelPath := range slices.Backward(targetRelPaths) {
		if !s.removeDirs.Contains(targetRelPath) {
			continue
		}

		// Ensure that we are attempting to remove a directory, not any other entry type.
		targetAbsPath := targetDirAbsPath.Join(targetRelPath)
		switch fileInfo, err := targetSystem.Stat(targetAbsPath); {
		case errors.Is(err, fs.ErrNotExist):
			continue TARGET
		case err != nil:
			return err
		case !fileInfo.IsDir():
			return fmt.Errorf("%s: not a directory", targetAbsPath)
		}

		// Attempt to remove the directory, but ignore any "not exist" or "not
		// empty" errors.
		switch err := targetSystem.Remove(targetAbsPath); {
		case err == nil:
			// Do nothing.
		case errors.Is(err, fs.ErrExist):
			continue TARGET
		case errors.Is(err, fs.ErrNotExist):
			continue TARGET
		default:
			return err
		}
		entryState := &EntryState{
			Type: EntryStateTypeRemove,
		}
		if err := PersistentStateSet(persistentState, EntryStateBucket, targetAbsPath.Bytes(), entryState); err != nil {

View on GitHub (pinned to f901167e46)

Solutions

  1. Reconcile the target: if it should be a directory, remove the stray file and recreate the directory ('rm path && mkdir path'), or if it should be a file, remove the stale directory entry from the source state.
  2. Run 'chezmoi apply --dry-run' or 'chezmoi diff' to inspect the mismatch before fixing.
  3. Correct the source entry type in the source directory (e.g. rename file to hello/ prefix or remove the dir entry).

Example fix

// before: source has a dir entry but target is a file
$ file ~/.local/share/chezmoi/dot_config   # entry thinks dir, target is file
// after
$ rm ~/.config/.../stale_file && mkdir -p matching/dir || adjust source entry type
Defensive patterns

Strategy: validation

Validate before calling

p="$1"
[ -d "$p" ] || { echo "$p is not a directory" >&2; exit 1; }

Prevention

When it happens

Trigger: Calling the remove/unmanage path (e.g. 'chezmoi forget' / removing source entries) where a target path recorded as a directory in the source state is now a plain file on the target filesystem, or vice versa mismatch.

Common situations: A file replaced a directory (or the reverse) outside of chezmoi; partial state after an interrupted apply; symlinks pointing at files where directories were expected.

Related errors


AI-assisted analysis of twpayne/chezmoi@f901167e46 (2026-09-01). Data as JSON: /api/errors/ece5170bfa3c40e8. Report an issue: GitHub.