hashicorp/nomad · error

failed to open %s to check for modifications

Error message

failed to open %s to check for modifications

What it means

This error is returned by ResolvConf.UserModified when it successfully parsed the stored digest and built a verifier, but os.Open on the actual resolv.conf (rcPath) fails. To compare the stored hash against the live file the library must open it; a failure to open means the modification check cannot be performed, so the underlying error is wrapped and returned.

Source

Thrown at lib/resolvconf/lib.go:425

// and false with an error if the result cannot be determined.
func UserModified(rcPath, rcHashPath string) (bool, error) {
	currRCHash, err := os.ReadFile(rcHashPath)
	if err != nil {
		// If the hash file doesn't exist, can only assume it hasn't been written
		// yet (so, the user hasn't modified the file it hashes).
		if errors.Is(err, fs.ErrNotExist) {
			return false, nil
		}
		return false, errors.Wrapf(err, "failed to read hash file %s", rcHashPath)
	}
	expected, err := digest.Parse(string(currRCHash))
	if err != nil {
		return false, errors.Wrapf(err, "failed to parse hash file %s", rcHashPath)
	}
	v := expected.Verifier()
	currRC, err := os.Open(rcPath)
	if err != nil {
		return false, errors.Wrapf(err, "failed to open %s to check for modifications", rcPath)
	}
	defer currRC.Close()
	if _, err := io.Copy(v, currRC); err != nil {
		return false, errors.Wrapf(err, "failed to hash %s to check for modifications", rcPath)
	}
	return !v.Verified(), nil
}

func (rc *ResolvConf) processLine(line string) {
	fields := strings.Fields(line)

	// Strip blank lines and comments.
	if len(fields) == 0 || fields[0][0] == '#' || fields[0][0] == ';' {
		return
	}

	switch fields[0] {
	case "nameserver":

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Verify rcPath exists and is a readable regular file: ls -l <rcPath>; recreate resolv.conf or fix the dangling symlink.
  2. Fix permissions so the process user can read rcPath (it is normally world-readable 0644).
  3. Check for file-descriptor leaks if the error is 'too many open files' and raise ulimit -n if legitimately needed.
  4. In containers, ensure /etc/resolv.conf is mounted/managed as expected (not a dangling symlink into an unmounted volume).

Example fix

// before: dangling symlink
// ls -l /etc/resolv.conf -> broken symlink to /run/systemd/resolve/stub-resolv.conf
// UserModified() -> failed to open ... to check for modifications

// after
// sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
// UserModified() -> (true/false, nil)
Defensive patterns

Strategy: validation

Validate before calling

if fi, err := os.Stat(rcPath); err != nil {
	// rcPath missing or inaccessible before calling UserModified
} else if !fi.Mode().IsRegular() {
	// not a regular file (symlink/directory)
}

Type guard

func resolvConfReadable(path string) bool {
	fi, err := os.Stat(path)
	return err == nil && fi.Mode().IsRegular()
}

Try / catch

modified, err := rc.UserModified()
if err != nil && strings.Contains(err.Error(), "failed to open") {
	if errors.Is(err, fs.ErrNotExist) {
		// resolv.conf missing: recreate default or skip check
	}
	return err
}

Prevention

When it happens

Trigger: Calling ResolvConf.UserModified when rcPath does not exist, the process lacks read permission on it, rcPath is a dangling symlink, or opening the path fails due to file-descriptor exhaustion or filesystem errors.

Common situations: resolv.conf deleted or replaced by a dangling symlink in containers where it is bind-mounted; running as non-root while resolv.conf is root-only; EMFILE in long-running daemons; missing mount of /etc/resolv.conf in minimal containers/chroots.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/dc5cc9b3db5c3284. Report an issue: GitHub.