thanos-io/thanos · warning

stat file

Error message

stat file: %s

What it means

Wraps an os.Stat failure on a per-entry path inside the config directory during apply(). After listing entries, the reloader stats each (following symlinks) to determine if it is a directory; a stat error aborts the whole apply pass.

Solutions

  1. Remove or repair broken symlinks in the config directory (find -xtype l)
  2. Re-run apply; transient races during atomic renames usually resolve on next poll
  3. Ensure the process user can stat all entries in the directory
  4. Avoid deleting entries while the reloader is walking (use atomic rename-in-place instead)

Example fix

// before
os.Symlink("/data/gone.yaml", "/etc/thanos/rules.d/r.yaml")
// after
if _, err := os.Stat("/data/gone.yaml"); err == nil {
    os.Symlink("/data/gone.yaml", "/etc/thanos/rules.d/r.yaml")
}
Defensive patterns

Strategy: retry

Validate before calling

if err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error {
    if err != nil { return err }
    return nil
}); err != nil { return err }

Type guard

func entryExists(path string) bool {
    _, err := os.Stat(path)
    return !os.IsNotExist(err)
}

Try / catch

if _, err := os.Stat(path); err != nil {
    if os.IsNotExist(err) {
        // entry vanished mid-walk: log and continue to next entry
        return nil
    }
    return errors.Wrapf(err, "stat file: %s", path)
}

Prevention

When it happens

Trigger: An entry in the watched directory disappears between ReadDir and os.Stat (race), or is a broken symlink whose target is gone, or has unreadable parent permissions.

Common situations: Config files being atomically swapped (rename over) while the reloader walks; dangling symlink to a deleted target; NFS stale handles.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/42db31ddbc2a5f90. Report an issue: GitHub.

Appendix: source

Thrown at pkg/reloader/reloader.go:446

			return errors.Wrap(err, "dir symlink eval")
		}
		outDir, err := filepath.EvalSymlinks(cfgDir.OutputDir)
		if err != nil {
			return errors.Wrap(err, "dir symlink eval")
		}

		cfgDirFiles := map[string]struct{}{}
		entries, err := os.ReadDir(walkDir)
		if err != nil {
			return errors.Wrapf(err, "read dir: %s", walkDir)
		}
		for _, entry := range entries {
			path := filepath.Join(walkDir, entry.Name())

			// Make sure to follow a symlink before checking if it is a directory.
			targetFile, err := os.Stat(path)
			if err != nil {
				return errors.Wrapf(err, "stat file: %s", path)
			}

			if targetFile.IsDir() {
				continue
			}

			if err := hashFile(h, path); err != nil {
				return errors.Wrapf(err, "build hash for file: %s", path)
			}

			outFile := filepath.Join(outDir, targetFile.Name())
			cfgDirFiles[outFile] = struct{}{}
			if err := r.normalize(path, outFile); err != nil {
				return errors.Wrapf(err, "move file: %s", path)
			}
		}
		if r.lastCfgDirFiles[i] != nil {
			if !maps.Equal(r.lastCfgDirFiles[i], cfgDirFiles) {

View on GitHub (pinned to 35b8b99117)