{"record":{"id":"ece5170bfa3c40e8","repo":"twpayne/chezmoi","slug":"s-not-a-directory","errorCode":null,"errorMessage":"%s: not a directory","messagePattern":"(.+?): not a directory","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/chezmoi/sourcestate.go","lineNumber":967,"sourceCode":") error {\n\t// Remove empty directories with the remove_ attribute. This assumes that\n\t// targetRelPaths is already sorted and iterates in reverse order so that\n\t// children are removed before their parents.\nTARGET:\n\tfor _, targetRelPath := range slices.Backward(targetRelPaths) {\n\t\tif !s.removeDirs.Contains(targetRelPath) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Ensure that we are attempting to remove a directory, not any other entry type.\n\t\ttargetAbsPath := targetDirAbsPath.Join(targetRelPath)\n\t\tswitch fileInfo, err := targetSystem.Stat(targetAbsPath); {\n\t\tcase errors.Is(err, fs.ErrNotExist):\n\t\t\tcontinue TARGET\n\t\tcase err != nil:\n\t\t\treturn err\n\t\tcase !fileInfo.IsDir():\n\t\t\treturn fmt.Errorf(\"%s: not a directory\", targetAbsPath)\n\t\t}\n\n\t\t// Attempt to remove the directory, but ignore any \"not exist\" or \"not\n\t\t// empty\" errors.\n\t\tswitch err := targetSystem.Remove(targetAbsPath); {\n\t\tcase err == nil:\n\t\t\t// Do nothing.\n\t\tcase errors.Is(err, fs.ErrExist):\n\t\t\tcontinue TARGET\n\t\tcase errors.Is(err, fs.ErrNotExist):\n\t\t\tcontinue TARGET\n\t\tdefault:\n\t\t\treturn err\n\t\t}\n\t\tentryState := &EntryState{\n\t\t\tType: EntryStateTypeRemove,\n\t\t}\n\t\tif err := PersistentStateSet(persistentState, EntryStateBucket, targetAbsPath.Bytes(), entryState); err != nil {","sourceCodeStart":949,"sourceCodeEnd":985,"githubUrl":"https://github.com/twpayne/chezmoi/blob/f901167e4685db90da56d6a2a19df642cb3e0247/internal/chezmoi/sourcestate.go#L949-L985","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Run 'chezmoi apply --dry-run' or 'chezmoi diff' to inspect the mismatch before fixing.","Correct the source entry type in the source directory (e.g. rename file to hello/ prefix or remove the dir entry)."],"exampleFix":"// before: source has a dir entry but target is a file\n$ file ~/.local/share/chezmoi/dot_config   # entry thinks dir, target is file\n// after\n$ rm ~/.config/.../stale_file && mkdir -p matching/dir || adjust source entry type","handlingStrategy":"validation","validationCode":"p=\"$1\"\n[ -d \"$p\" ] || { echo \"$p is not a directory\" >&2; exit 1; }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep target filesystem types in sync with source entries","Run 'chezmoi diff' before bulk operations","Avoid manually swapping files for directories under management"],"tags":["chezmoi","filesystem","type-mismatch"],"backgroundTag":"path-type-mismatch","analyzedSha":"f901167e4685db90da56d6a2a19df642cb3e0247","analyzedAt":"2026-09-01T18:16:41.508Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T01:17:15.007Z"}