lima-vm/lima · error

resolving child OID %d: %w

Error message

resolving child OID %d: %w

What it means

This error wraps a failure from omapLookup while descending an internal B-tree node in lookupDirEntry: the child block's virtual OID could not be translated to a physical address through the object map. The %d is the virtual OID and %w the underlying omap error (typically 'not found' or an omap node failure).

Source

Thrown at pkg/apfs/chown.go:585

		// name hash to find the correct subtree.
		childIdx := uint32(0)
		for i := range nkeys {
			kOff, _ := c.readTocEntry(blk, tocStart, i, isFixedKV)
			keyStart := keyAreaStart + kOff
			cmp := c.compareDrecKey(blk, keyStart, targetKeyHeader, name, targetHash)
			if cmp <= 0 {
				childIdx = i
			} else {
				break
			}
		}

		// Read child OID (virtual) and resolve through omap.
		_, vOff := c.readTocEntry(blk, tocStart, childIdx, isFixedKV)
		childOID := le.Uint64(blk[valueAreaEnd-vOff:])
		childPhys, err := c.omapLookup(omapTreeAddr, childOID, maxXID)
		if err != nil {
			return 0, fmt.Errorf("resolving child OID %d: %w", childOID, err)
		}
		blk, err = c.readBlock(childPhys)
		if err != nil {
			return 0, err
		}
	}
}

// readDrecName reads a directory record name from the key data
// starting at nameFieldOff. Handles both hashed (j_drec_hashed_key_t,
// 4-byte name_len_and_hash) and non-hashed (j_drec_key_t, 2-byte
// name_len) formats.
func (c *container) readDrecName(blk []byte, nameFieldOff uint32) string {
	// Heuristic: if the 4-byte field at nameFieldOff has its upper
	// bits set (hash), it's a hashed key. For non-hashed keys, the
	// 2-byte name_len is followed by the name bytes.
	//
	// In j_drec_hashed_key_t: name_len_and_hash (uint32) where

View on GitHub (pinned to dd909d0973)

Solutions

  1. Use the max XID (latest transaction) consistent with the tree you are walking
  2. Pass the omap tree address belonging to the same volume/superblock as fsRootPhys
  3. Run fsck_apfs to repair a damaged omap
  4. Re-read the container superblock and re-resolve both tree roots before walking
Defensive patterns

Strategy: retry

Validate before calling

// Use the latest transaction ID so omap entries exist
maxXID := readContainerMaxXID(containerSuperblock)
if maxXID == 0 {
    return errors.New("invalid maxXID: use latest container transaction")
}

Type guard

func isOmapResolutionError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "resolving child OID")
}

Try / catch

inode, err := c.resolvePath(root, omap, maxXID, path)
if isOmapResolutionError(err) {
    // re-read superblock for fresh tree roots and retry once
    fsRoot, omapTree, maxXID = reResolveRoots()
    inode, err = c.resolvePath(fsRoot, omapTree, maxXID, path)
}

Prevention

When it happens

Trigger: resolvePath descends into a child node whose virtual OID is absent from the omap tree for the given maxXID — the child was created after maxXID (stale transaction snapshot), the wrong omap tree address was supplied, or the omap entry was deleted by a rebalance/cleanup.

Common situations: Reading an old snapshot (maxXID too low) while the live tree was modified; passing the live omap root to a snapshot tree walk; corrupted or mismatched container superblock selection on multi-volume images.

Related errors


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