hashicorp/nomad · error

failed to hash %s to check for modifications

Error message

failed to hash %s to check for modifications

What it means

This error is returned by ResolvConf.UserModified when opening resolv.conf succeeded but streaming its contents into the digest verifier via io.Copy failed. The library computes the live file's hash by copying it into the verifier; an I/O error mid-read means the comparison cannot be completed and the error is wrapped and returned.

Source

Thrown at lib/resolvconf/lib.go:429

		// 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":
		if len(fields) < 2 {
			return
		}
		if addr, err := netip.ParseAddr(fields[1]); err != nil {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Check dmesg/journal for I/O errors on the backing storage and fix the disk/filesystem issue (fsck, replace failing hardware).
  2. Retry UserModified — a transient read race with resolv.conf being replaced usually resolves on a second attempt.
  3. Avoid concurrent replacement of rcPath while reading (pause resolvconf/systemd-resolved updates or pin the file).
  4. If on a network/overlay filesystem, move state and resolv.conf to local stable storage.

Example fix

// before: rcPath replaced mid-read
// UserModified() -> failed to hash ... to check for modifications

// after: retry with backoff
// var modified bool
// for i := 0; i < 3; i++ {
// 	modified, err = rc.UserModified()
// 	if err == nil { break }
// 	time.Sleep(100 * time.Millisecond)
// }
Defensive patterns

Strategy: retry

Validate before calling

if f, err := os.Open(rcPath); err != nil {
	// cannot open; don't bother calling UserModified
} else {
	f.Close()
}

Type guard

func canReadFully(path string) bool {
	f, err := os.Open(path)
	if err != nil { return false }
	defer f.Close()
	_, err = io.Copy(io.Discard, f)
	return err == nil
}

Try / catch

var modified bool
var lastErr error
for i := 0; i < 3; i++ {
	modified, lastErr = rc.UserModified()
	if lastErr == nil || !strings.Contains(lastErr.Error(), "failed to hash") {
		break
	}
	time.Sleep(100 * time.Millisecond << i)
}
return modified, lastErr

Prevention

When it happens

Trigger: Calling ResolvConf.UserModified when reading rcPath fails partway through: disk I/O errors, the file being replaced/truncated concurrently (e.g. resolv.conf rewritten while reading), or a network/overlay filesystem returning EIO.

Common situations: Underlying storage failure on the host; container runtime or systemd-resolved replacing the resolv.conf bind-mount mid-read; NFS/overlayfs flakiness; hardware errors visible in dmesg.

Related errors


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