tailscale/tailscale · error

reading %x: %v

Error message

reading %x: %v

What it means

While scanning storage (FS.scanHashes), a file whose name IS a valid AUM hash could not be read via FS.get: open failure, CBOR decode failure, or the decoded AUM not matching its filename hash. Note the distinction: files with non-hash names are merely logged and skipped ('ignoring unexpected non-AUM'); this error means a hash-named AUM file is genuinely broken or vanished between listing and reading.

Source

Thrown at tka/tailchonk.go:605

		files, err := os.ReadDir(filepath.Join(c.base, prefix.Name()))
		if err != nil {
			return fmt.Errorf("reading prefix dir: %v", err)
		}
		for _, file := range files {
			// Ignore files whose names aren't valid AUM hashes, which may be
			// temporary files which are partway through being written, or other
			// files added by the OS (like .DS_Store) which we can ignore.
			// TODO(alexc): it might be useful to append a suffix like `.aum` to
			// filenames, so we can more easily distinguish between AUMs and
			// arbitrary other files.
			var h AUMHash
			if err := h.UnmarshalText([]byte(file.Name())); err != nil {
				log.Printf("ignoring unexpected non-AUM: %s: %v", file.Name(), err)
				continue
			}
			info, err := c.get(h)
			if err != nil {
				return fmt.Errorf("reading %x: %v", h, err)
			}
			if info.PurgedUnix > 0 {
				continue
			}

			eachHashInfo(info)
		}
	}

	return nil
}

// SetLastActiveAncestor is called to record the oldest-known AUM
// that contributed to the current state. This value is used as
// a hint on next startup to determine which chain to pick when computing
// the current state, if there are multiple distinct chains.
func (c *FS) SetLastActiveAncestor(hash AUMHash) error {
	c.mu.Lock()

View on GitHub (pinned to 6e0912f979)

Solutions

  1. Unwrap the cause: not-exist points to a concurrent delete; decode/hash-mismatch points to corruption.
  2. Restore or re-sync the named AUM from a healthy peer, then reopen the FS to rescan.
  3. If concurrent deletion is by design (external purging), serialize it with FS access or reopen after it completes.
Defensive patterns

Strategy: try-catch

Try / catch

if err := fs.Heads(); err != nil { // triggers scanHashes
	if errors.Is(err, fs.ErrNotExist) {
		// AUM file deleted between listing and read: retry after reopen
	}
	return err // decode/hash-mismatch => corruption, restore from peer
}

Prevention

When it happens

Trigger: An index rebuild encountering a truncated/corrupted AUM file, a hash-named file containing junk, or a file deleted concurrently with the scan (get() then fails with not-exist).

Common situations: Partial writes from a crash; interrupted copies of the TKA dir; files purged by external tooling mid-scan; disk corruption.

Related errors


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/525ee1ae78d5c1a5. Report an issue: GitHub.