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
- Unwrap the cause: not-exist points to a concurrent delete; decode/hash-mismatch points to corruption.
- Restore or re-sync the named AUM from a healthy peer, then reopen the FS to rescan.
- 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
- Write AUM files atomically (temp + rename) if you ever produce them; partial files fail the scan.
- Do not place non-AUM content in hash-named files — hash-named junk is fatal, other names are skipped.
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
- reading child %d of %x: %w
- reading head %x: %w
- reading prefix dir: %v
- computeStateAt: %v
- local syncOffer: %v
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/525ee1ae78d5c1a5.
Report an issue: GitHub.