{"id":"11ad8583d7798002","repo":"gohugoio/hugo","slug":"q-is-not-a-directory","errorCode":null,"errorMessage":"%q is not a directory","messagePattern":"%q is not a directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/hugio/copy.go","lineNumber":61,"sourceCode":"\t\terr = fs.Chmod(to, si.Mode())\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// CopyDir copies a directory.\nfunc CopyDir(fs afero.Fs, from, to string, shouldCopy func(filename string) bool) error {\n\tfi, err := fs.Stat(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !fi.IsDir() {\n\t\treturn fmt.Errorf(\"%q is not a directory\", from)\n\t}\n\n\terr = fs.MkdirAll(to, 0o777) // before umask\n\tif err != nil {\n\t\treturn err\n\t}\n\n\td, err := fs.Open(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\tentries, _ := d.(iofs.ReadDirFile).ReadDir(-1)\n\tfor _, entry := range entries {\n\t\tfromFilename := filepath.Join(from, entry.Name())\n\t\ttoFilename := filepath.Join(to, entry.Name())\n\t\tif entry.IsDir() {\n\t\t\tif shouldCopy != nil && !shouldCopy(fromFilename) {\n\t\t\t\tcontinue","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/common/hugio/copy.go#L43-L79","documentation":"hugio.CopyDir stats the source path and requires it to be a directory before recursively copying entries; if the path exists but is a regular file, it fails fast with this error rather than silently copying a single file. This guards internal copy operations (e.g. hugo new site/theme scaffolding, module and resource copying) on the afero filesystem abstraction.","triggerScenarios":"Any internal call to hugio.CopyDir(fs, from, to, ...) where `from` resolves to a file, not a directory — e.g. a scaffolding or mount path that points at a file, or an overlay/union filesystem entry that materializes as a file.","commonSituations":"Misconfigured module mounts or commands where a source path intended as a directory is actually a file (or a symlink to a file), stale paths after restructuring a project, or passing a file path to a Hugo command/API expecting a directory.","solutions":["Check the reported path — it exists but is a file; point the configuration/command at its parent directory or the intended directory.","If a symlink is involved, ensure it targets a directory.","If the path should be a directory, recreate the expected structure (move the file inside a directory of that name)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"fi, err := os.Stat(from)\nif err != nil {\n    return err\n}\nif !fi.IsDir() {\n    return fmt.Errorf(\"%q is not a directory\", from)\n}","typeGuard":"func isDir(fs afero.Fs, path string) bool {\n    fi, err := fs.Stat(path)\n    return err == nil && fi.IsDir()\n}","tryCatchPattern":"if err := hugio.CopyDir(fs, from, to, nil); err != nil {\n    if os.IsNotExist(err) || strings.Contains(err.Error(), \"is not a directory\") {\n        // caller passed a file or missing path where a directory was expected\n    }\n    return err\n}","preventionTips":["Stat the source path and check IsDir() before calling CopyDir","Use CopyFile for files and CopyDir for directories — don't guess","Build paths with filepath.Join from verified roots rather than string concatenation"],"tags":["hugo","filesystem","copy","io"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}