{"id":"601dbf054c13be89","repo":"gohugoio/hugo","slug":"failed-to-read-directory-q-s","errorCode":null,"errorMessage":"failed to read directory %q: %s","messagePattern":"failed to read directory %q: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tpl/os/os.go","lineNumber":125,"sourceCode":"\tif err != nil && herrors.IsNotExist(err) {\n\t\treturn \"\", nil\n\t}\n\treturn s, err\n}\n\n// ReadDir lists the directory contents relative to the configured WorkingDir.\nfunc (ns *Namespace) ReadDir(i any) ([]_os.FileInfo, error) {\n\tpath, err := cast.ToStringE(i)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlist, err := afero.ReadDir(ns.workFs, path)\n\tif err != nil {\n\t\tif herrors.IsNotExist(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"failed to read directory %q: %s\", path, err)\n\t}\n\n\treturn list, nil\n}\n\n// FileExists checks whether a file exists under the given path.\nfunc (ns *Namespace) FileExists(i any) (bool, error) {\n\tpath, err := cast.ToStringE(i)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif path == \"\" {\n\t\treturn false, nil\n\t}\n\n\tstatus, err := afero.Exists(ns.readFileFs, path)\n\tif err != nil {","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/tpl/os/os.go#L107-L143","documentation":"`os.ReadDir` failed to list the given path for a reason other than non-existence (non-existent paths return nil silently). The underlying afero filesystem error — typically 'not a directory' or a permission error — is wrapped with the path.","triggerScenarios":"`{{ range os.ReadDir $p }}` where $p points to a regular file instead of a directory, or a directory the Hugo process lacks permission to read; symlink targets outside the allowed filesystem.","commonSituations":"Passing a file path (e.g. \"content/post.md\") where a directory is expected; permission problems in CI/containers; symlinked content directories with restrictive security settings; path casing mismatches on case-sensitive filesystems combined with an unexpected file at that name.","solutions":["Verify the path is a directory relative to the project working directory: `os.ReadDir \"content/posts\"`.","Check the wrapped underlying error text — 'not a directory' means fix the path; 'permission denied' means fix filesystem permissions.","On CI, ensure the checkout/mount preserves directory permissions and symlinks resolve inside the project."],"exampleFix":"{{/* before */}}\n{{ range os.ReadDir \"content/posts/first.md\" }}...{{ end }}\n{{/* after */}}\n{{ range os.ReadDir \"content/posts\" }}...{{ end }}","handlingStrategy":"try-catch","validationCode":"{{ if fileExists \"static/images\" }}\n  {{ range readDir \"static/images\" }}...{{ end }}\n{{ end }}","typeGuard":null,"tryCatchPattern":"{{ with try (os.ReadDir $dir) }}\n  {{ with .Err }}{{ warnf \"readDir %q failed: %s\" $dir . }}{{ else }}{{ range .Value }}...{{ end }}{{ end }}\n{{ end }}","preventionTips":["Verify the directory exists with fileExists before calling readDir","Pass a directory, not a file — os.Stat first if unsure ((os.Stat $p).IsDir)","Remember readDir resolves relative to the project root, not the current template's directory"],"tags":["hugo","templates","os","file-io","directory"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}