lima-vm/lima · critical

filesystem B-tree node checksum failed

Error message

filesystem B-tree node checksum failed

What it means

This error is thrown by lookupDirEntry when the on-disk checksum (fletcher/CRC) of an APFS B-tree node block read during a directory-entry lookup does not match. The library verifies every B-tree node it touches before parsing it, because a corrupt node would produce garbage directory entries. It indicates block-level corruption of the filesystem tree being walked, not a caller mistake.

Source

Thrown at pkg/apfs/chown.go:519

		cnid = fileID
	}
	return cnid, nil
}

// lookupDirEntry searches the filesystem B-tree for a directory record
// matching parentCNID and name, returning the file_id from j_drec_val_t.
func (c *container) lookupDirEntry(fsRootPhys, omapTreeAddr, maxXID, parentCNID uint64, name string) (uint64, error) {
	targetKeyHeader := (uint64(apfsTypeDirRec) << objTypeShift) | (parentCNID & objIDMask)
	targetHash := drecNameHash(name)

	blk, err := c.readBlock(fsRootPhys)
	if err != nil {
		return 0, err
	}

	for {
		if verifyChecksum(blk) != nil {
			return 0, errors.New("filesystem B-tree node checksum failed")
		}
		if err := verifyBTreeNodeType(blk); err != nil {
			return 0, fmt.Errorf("filesystem B-tree node: %w", err)
		}
		flags := le.Uint16(blk[btnFlagsOff:])
		nkeys := le.Uint32(blk[btnNKeysOff:])
		tspOff := le.Uint16(blk[btnTableSpaceOff:])
		tspLen := le.Uint16(blk[btnTableSpaceOff+2:])

		tocStart := btnDataOff + uint32(tspOff)
		keyAreaStart := tocStart + uint32(tspLen)

		isLeaf := flags&btnodeLeaf != 0
		isFixedKV := flags&btnodeFixedKVSize != 0
		isRoot := flags&btnodeRoot != 0

		valueAreaEnd := c.blockSize
		if isRoot {

View on GitHub (pinned to dd909d0973)

Solutions

  1. Run fsck_apfs on the volume/image to detect and repair the corrupt B-tree node
  2. Restore the disk image or volume from a backup/snapshot taken before the corruption
  3. Verify the image file is not truncated and its size matches the APFS container description
  4. Re-run the lookup with the correct max XID / omap tree so current, not stale, blocks are resolved
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify image integrity before walking
if fi, err := os.Stat(imagePath); err != nil || fi.Size() < expectedMinSize {
    return fmt.Errorf("image %s missing or truncated", imagePath)
}

Type guard

func isBTreeChecksumFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "B-tree node checksum failed")
}

Try / catch

inode, err := c.resolvePath(root, omap, maxXID, path)
if isBTreeChecksumFailure(err) {
    // block-level corruption: surface as fatal, suggest fsck_apfs
    return fmt.Errorf("volume corrupt, run fsck_apfs: %w", err)
}

Prevention

When it happens

Trigger: Calling resolvePath (which calls lookupDirEntry) on a volume whose directory B-tree blocks were corrupted on disk: partial/interrupted write, bit rot, bad disk sector, or reading a stale/mismatched physical block address after the tree was rebalanced or the snapshot changed under the reader.

Common situations: Reading an APFS disk image that was truncated or copied while the host was writing; inspecting a snapshot after the underlying disk developed bad sectors; mounting with a wrong omap/XID so a stale block (old checksum context) is read.

Related errors


AI-assisted analysis of lima-vm/lima@dd909d0973 (2026-09-01). Data as JSON: /api/errors/a0e59012bb74f5aa. Report an issue: GitHub.