pranshuparmar/witr · error

listfds returned partial record

Error message

listfds returned partial record

What it means

readDarwinFDs calls proc_pidinfo with the PROC_PIDLISTFDS selector to enumerate a process's file descriptors. The kernel returned a byte count that is not an exact multiple of the per-entry struct size (proc_fdinfo), meaning the fd list changed while it was being read or the buffer interpretation is inconsistent. Rather than returning a truncated/corrupt entry array, the library fails fast with this error.

Source

Thrown at internal/proc/libproc_darwin_cgo.go:177

	for {
		var used C.int
		errno := C.witr_proc_pidlistfds(C.int(pid), &entries[0], C.int(len(entries)*bytesPerEntry), &used)
		if errno == C.EINVAL && len(entries) < 16384 {
			entries = make([]C.struct_proc_fdinfo, len(entries)*2)
			continue
		}
		if errno != 0 {
			switch errno {
			case C.ESRCH, C.EPERM:
				return 0, nil, nil
			default:
				return 0, nil, fmt.Errorf("proc_pidinfo listfds: %d", errno)
			}
		}
		bytesUsed := int(used)
		if bytesUsed%bytesPerEntry != 0 {
			return 0, nil, errors.New("listfds returned partial record")
		}
		count := bytesUsed / bytesPerEntry
		return count, formatFDEntries(pid, entries[:count]), nil
	}
}

func formatFDEntries(pid int, entries []C.struct_proc_fdinfo) []string {
	var out []string
	for _, entry := range entries {
		if len(out) >= 10 {
			break
		}
		fd := int(entry.proc_fd)
		label := fdTypeLabel(entry.proc_fdtype)
		switch entry.proc_fdtype {
		case C.PROX_FDTYPE_VNODE:
			var vnode C.struct_vnode_fdinfowithpath
			if errno := C.witr_proc_pidfdinfo_vnode(C.int(pid), C.int(fd), &vnode); errno == 0 {

View on GitHub (pinned to dc4fa1da82)

Solutions

  1. Retry the fd snapshot; a transient race is the usual cause and a second read typically returns a consistent record set.
  2. Freeze or quiesce the target (SIGSTOP, or run against a process with stable fd usage) before enumerating.
  3. Retry a few times with a small backoff in your monitoring code before surfacing the failure.
  4. If it reproduces on a quiesced process, verify macOS/Xcode SDK version matches what the library's proc_fdinfo sizing assumes and file a bug.

Example fix

// before
fds, err := readDarwinFDs(pid)
if err != nil { return err }
// after
var fds []FDInfo
var err error
for i := 0; i < 3; i++ {
    fds, err = readDarwinFDs(pid)
    if err == nil || !strings.Contains(err.Error(), "partial record") { break }
    time.Sleep(50 * time.Millisecond) // fd table churn; retry the snapshot
}
Defensive patterns

Strategy: retry

Try / catch

var fds []FDInfo
var lastErr error
for i := 0; i < 3; i++ {
    fds, lastErr = readDarwinFDs(pid)
    if lastErr == nil || !strings.Contains(lastErr.Error(), "partial record") {
        break
    }
    time.Sleep(50 * time.Millisecond)
}
if lastErr != nil {
    return fmt.Errorf("fd snapshot for pid %d failed after retries: %w", pid, lastErr)
}

Prevention

When it happens

Trigger: Calling readDarwinFDs (the fds / open-files inspection on macOS) when the target process opens or closes file descriptors concurrently with the proc_pidinfo(PROC_PIDLISTFDS) call, so the reported bytesUsed is not divisible by bytesPerEntry.

Common situations: Inspecting a high-churn process (a server constantly accepting/closing sockets, a logging daemon rotating files); inspecting a short-lived process that exits during enumeration; racing snapshots in a monitoring loop.

Related errors


AI-assisted analysis of pranshuparmar/witr@dc4fa1da82 (2026-09-01). Data as JSON: /api/errors/e16332016722f392. Report an issue: GitHub.