{"record":{"id":"6c086e6058fa9bb5","repo":"thanos-io/thanos","slug":"read-dir-s","errorCode":null,"errorMessage":"read dir: %s","messagePattern":"read dir: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/reloader/reloader.go","lineNumber":438,"sourceCode":"\n\tcfgDirsHash := make([][]byte, len(r.cfgDirs))\n\tcfgDirsChanged := len(r.lastCfgDirsHash) == 0 && len(r.cfgDirs) > 0\n\tfor i, cfgDir := range r.cfgDirs {\n\t\th := sha256.New()\n\n\t\twalkDir, err := filepath.EvalSymlinks(cfgDir.Dir)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"dir symlink eval\")\n\t\t}\n\t\toutDir, err := filepath.EvalSymlinks(cfgDir.OutputDir)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"dir symlink eval\")\n\t\t}\n\n\t\tcfgDirFiles := map[string]struct{}{}\n\t\tentries, err := os.ReadDir(walkDir)\n\t\tif err != nil {\n\t\t\treturn errors.Wrapf(err, \"read dir: %s\", walkDir)\n\t\t}\n\t\tfor _, entry := range entries {\n\t\t\tpath := filepath.Join(walkDir, entry.Name())\n\n\t\t\t// Make sure to follow a symlink before checking if it is a directory.\n\t\t\ttargetFile, err := os.Stat(path)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrapf(err, \"stat file: %s\", path)\n\t\t\t}\n\n\t\t\tif targetFile.IsDir() {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif err := hashFile(h, path); err != nil {\n\t\t\t\treturn errors.Wrapf(err, \"build hash for file: %s\", path)\n\t\t\t}\n","sourceCodeStart":420,"sourceCodeEnd":456,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/pkg/reloader/reloader.go#L420-L456","documentation":"Wraps an os.ReadDir failure while the reloader walks a config directory in apply(). The reloader periodically re-reads the watched directory to detect config changes; if the directory itself cannot be listed, apply aborts with this wrapped error including the directory path.","triggerScenarios":"Watch's apply() calls os.ReadDir(walkDir) on a configured config directory and the OS returns an error: directory deleted mid-run, permission denied on the directory, or walkDir is not a directory (e.g. a file or dangling symlink).","commonSituations":"Config mount (Kubernetes ConfigMap volume) remounted/removed while Thanos is running; directory permissions changed; path typo so walkDir points at a file.","solutions":["Verify the watched directory path exists and is a directory (ls -ld <dir>)","Fix filesystem permissions so the process user can read the directory","If running on a K8s ConfigMap mount, ensure the mount point is not temporarily unmounted/remounted","Point the reloader at a stable parent directory that always exists"],"exampleFix":"// before\nwatchDir := \"/etc/thanos/rules.d\"\n// after — ensure dir exists before starting Watch\nif _, err := os.Stat(watchDir); os.IsNotExist(err) {\n    if err := os.MkdirAll(watchDir, 0o755); err != nil { log.Fatal(err) }\n}","handlingStrategy":"try-catch","validationCode":"if info, err := os.Stat(walkDir); err != nil || !info.IsDir() {\n    return fmt.Errorf(\"watch dir %s missing or not a directory\", walkDir)\n}","typeGuard":"func isDir(p string) bool {\n    info, err := os.Stat(p)\n    return err == nil && info.IsDir()\n}","tryCatchPattern":"if _, err := os.ReadDir(walkDir); err != nil {\n    var perr *fs.PathError\n    if errors.As(err, &perr) && os.IsNotExist(err) {\n        // recreate dir or skip this poll cycle and retry\n    }\n}","preventionTips":["Check directory existence and readability at startup before starting Watch","Avoid pointing the reloader at ephemeral mount points that get remounted","Monitor the error stream and alert on repeated read-dir failures"],"tags":["filesystem","config-reload","go"],"backgroundTag":"directory-not-found","analyzedSha":"35b8b991177def87ed52dcf10f9b6d87f07282c8","analyzedAt":"2026-09-07T01:49:59.689Z","contentChangedAt":"2026-09-07T01:49:59.689Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}