{"record":{"id":"d3d9e97f163c62c7","repo":"tailscale/tailscale","slug":"reading-d-x-w","errorCode":null,"errorMessage":"reading %d (%x): %w","messagePattern":"reading (.+?) \\(%x\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tka/tailchonk.go","lineNumber":691,"sourceCode":"\n\treturn nil\n}\n\n// PurgeAUMs marks the specified AUMs for deletion from storage.\nfunc (c *FS) PurgeAUMs(hashes []AUMHash) error {\n\tif len(hashes) == 0 {\n\t\treturn nil\n\t}\n\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\n\tc.invalidateIndexLocked()\n\tnow := time.Now()\n\tfor i, h := range hashes {\n\t\tstored, err := c.get(h)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"reading %d (%x): %w\", i, h, err)\n\t\t}\n\t\tif stored.AUM == nil || stored.PurgedUnix > 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\terr = c.commit(h, func(info *fsHashInfo) {\n\t\t\tinfo.PurgedUnix = now.Unix()\n\t\t})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"committing purge[%d] (%x): %w\", i, h, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// commit calls the provided updater function to record changes relevant\n// to the given hash. The caller is expected to update the AUM and\n// Children fields, as relevant.","sourceCodeStart":673,"sourceCodeEnd":709,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/tka/tailchonk.go#L673-L709","documentation":"Thrown by FS.PurgeAUMs while marking AUMs for deletion. Before setting the PurgedUnix flag, the chonk re-reads the stored record for each hash (a CBOR file named by hash under a 2-hex-char prefix directory). The error means the record for the i-th hash could not be read back. Note that os.ErrNotExist is NOT tolerated on this path: purging a hash that was never committed to this chonk, or whose file was deleted out-of-band, fails with this exact message.","triggerScenarios":"Calling PurgeAUMs with a delete list derived from another node's storage containing hashes this chonk never stored; the AUM file was removed by a second process or by hand between listing and purge; the stored file is corrupt so c.get()'s CBOR decode fails; permission changes prevent os.Open.","commonSituations":"tka.Compact computing a toDelete list while another process mutates the same state dir; state dir partially restored from backup; scripts pruning 'old' files in the tka directory by mtime.","solutions":["If the wrapped error is os.ErrNotExist the purge goal is already met: drop that hash from the list and call PurgeAUMs again","Ensure no second process is writing or deleting files in the chonk directory; stop the duplicate","If the file exists but will not decode, move it aside (filename = the hex hash in the message) and drop the hash from the list","Always recompute the delete list from this chonk's own AllAUMs(), never from a foreign node's state"],"exampleFix":"// before\nif err := chonk.PurgeAUMs(toDelete); err != nil {\n\treturn err // reading 3 (3f9a..): file does not exist\n}\n\n// after: only purge hashes that actually exist here\nkeep := toDelete[:0]\nfor _, h := range toDelete {\n\tif _, err := chonk.AUM(h); err == nil {\n\t\tkeep = append(keep, h)\n\t}\n}\nreturn chonk.PurgeAUMs(keep)","handlingStrategy":"validation","validationCode":"known, err := chonk.AllAUMs()\nif err != nil { return err }\nhave := make(map[tka.AUMHash]bool, len(known))\nfor _, h := range known { have[h] = true }\npurge := input[:0]\nfor _, h := range input {\n\tif have[h] { purge = append(purge, h) }\n}","typeGuard":"func isStored(chonk *tka.FS, h tka.AUMHash) bool {\n\t_, err := chonk.AUM(h)\n\treturn err == nil\n}","tryCatchPattern":"if err := chonk.PurgeAUMs(hashes); err != nil {\n\tif errors.Is(err, os.ErrNotExist) {\n\t\t// target already removed; purge goal already met — safe to continue\n\t\treturn nil\n\t}\n\treturn err\n}","preventionTips":["Derive purge lists from the same chonk's AllAUMs(), never from another node's state","Keep a single writer process per state dir","Never delete files inside the tka directory by hand"],"tags":["tailscale","tka","tailchonk","purge","filesystem","go"],"backgroundTag":"file-read-failed","analyzedSha":"6e0912f97994f927632b34ae9e63b53d6516a6ac","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}