{"record":{"id":"525ee1ae78d5c1a5","repo":"tailscale/tailscale","slug":"reading-x-v","errorCode":null,"errorMessage":"reading %x: %v","messagePattern":"reading %x: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tka/tailchonk.go","lineNumber":605,"sourceCode":"\t\tfiles, err := os.ReadDir(filepath.Join(c.base, prefix.Name()))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"reading prefix dir: %v\", err)\n\t\t}\n\t\tfor _, file := range files {\n\t\t\t// Ignore files whose names aren't valid AUM hashes, which may be\n\t\t\t// temporary files which are partway through being written, or other\n\t\t\t// files added by the OS (like .DS_Store) which we can ignore.\n\t\t\t// TODO(alexc): it might be useful to append a suffix like `.aum` to\n\t\t\t// filenames, so we can more easily distinguish between AUMs and\n\t\t\t// arbitrary other files.\n\t\t\tvar h AUMHash\n\t\t\tif err := h.UnmarshalText([]byte(file.Name())); err != nil {\n\t\t\t\tlog.Printf(\"ignoring unexpected non-AUM: %s: %v\", file.Name(), err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tinfo, err := c.get(h)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"reading %x: %v\", h, err)\n\t\t\t}\n\t\t\tif info.PurgedUnix > 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\teachHashInfo(info)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// SetLastActiveAncestor is called to record the oldest-known AUM\n// that contributed to the current state. This value is used as\n// a hint on next startup to determine which chain to pick when computing\n// the current state, if there are multiple distinct chains.\nfunc (c *FS) SetLastActiveAncestor(hash AUMHash) error {\n\tc.mu.Lock()","sourceCodeStart":587,"sourceCodeEnd":623,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/tka/tailchonk.go#L587-L623","documentation":"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.","triggerScenarios":"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).","commonSituations":"Partial writes from a crash; interrupted copies of the TKA dir; files purged by external tooling mid-scan; disk corruption.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := fs.Heads(); err != nil { // triggers scanHashes\n\tif errors.Is(err, fs.ErrNotExist) {\n\t\t// AUM file deleted between listing and read: retry after reopen\n\t}\n\treturn err // decode/hash-mismatch => corruption, restore from peer\n}","preventionTips":["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."],"tags":["go","tailscale","tka","storage","corruption","filesystem"],"backgroundTag":"data-corruption","analyzedSha":"6e0912f97994f927632b34ae9e63b53d6516a6ac","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}