go-delve/delve · error

wait err %s %d

Error message

wait err %s %d

What it means

In FreeBSD's trapWaitInternal, dbp.wait(pid, 0) returned a non-nil error while the debugger waited for the traced process to hit a trap or exit. Delve wraps the raw wait(2)/wait4 error with the watched pid so the caller knows which process the kernel reported a problem for. It indicates the wait syscall itself failed rather than a normal stop event.

Source

Thrown at pkg/proc/native/proc_freebsd.go:350

	trapWaitNormal trapWaitMode = iota
	trapWaitStepping
)

// Used by stop and trapWait
func (dbp *nativeProcess) trapWaitInternal(pid int, mode trapWaitMode) (*nativeThread, error) {
	if dbp.os.selectedThread != nil {
		th := dbp.os.selectedThread
		dbp.os.selectedThread = nil
		return th, nil
	}
	for {
		wpid, status, err := dbp.wait(pid, 0)
		if wpid != dbp.pid {
			// possibly a delayed notification from a process we just detached and killed, freebsd bug?
			continue
		}
		if err != nil {
			return nil, fmt.Errorf("wait err %s %d", err, pid)
		}
		if status.Killed() {
			// "Killed" status may arrive as a result of a Process.Kill() of some other process in
			// the system performed by the same tracer (e.g. in the previous test)
			continue
		}
		if status.Exited() {
			dbp.postExit()
			return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()}
		}
		if status.Signaled() {
			// Killed by a signal
			dbp.postExit()
			return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())}
		}

		var info sys.PtraceLwpInfoStruct
		dbp.execPtraceFunc(func() { info, err = ptraceGetLwpInfo(wpid) })

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Check whether another process (CI runner, supervisor) could have reaped or signaled the target; ensure only the debugger waits on the child pid.
  2. Retry the operation; transient EINTR from signals delivered to the debugger is usually harmless on retry.
  3. Verify the target pid still exists (ps / procstat) before attaching or continuing.
  4. If it reproduces after detaching from another process, it matches the known FreeBSD delayed-notification quirk noted in the surrounding code; update FreeBSD or delve.

Example fix

// before
tgt, err := dbp.Continue()
if err != nil { return err } // opaque 'wait err ...'

// after
tgt, err := dbp.Continue()
var exited proc.ErrProcessExited
if errors.As(err, &exited) {
    return nil // target already terminated, treat as clean exit
}
if err != nil { return err }
Defensive patterns

Strategy: retry

Validate before calling

// before continuing/attaching on FreeBSD
func pidWaitable(pid int) error {
    out, err := exec.Command("ps", "-o", "ppid=", "-p", strconv.Itoa(pid)).Output()
    if err != nil { return fmt.Errorf("pid %d not present", pid) }
    _ = out
    return nil
}

Try / catch

err := dbp.Continue()
if err != nil && strings.HasPrefix(err.Error(), "wait err ") {
    // transient wait failure: verify target then retry once
    if pidWaitable(pid) == nil {
        err = dbp.Continue()
    }
}

Prevention

When it happens

Trigger: nativeProcess.Launch/Attach/Continue flow on FreeBSD when trapWaitInternal calls dbp.wait(pid, 0) and the kernel returns an error, e.g. EINTR repeatedly, or the child pid is no longer a child of this tracer so wait4 returns ECHILD.

Common situations: The debugged process was already reaped by another process (double debugger attach, or a supervisor reaped it); the pid was killed between fork and wait; running under test harnesses that reap children; FreeBSD ptrace quirks after detach-and-kill of a previously traced process.

Related errors


AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31). Data as JSON: /api/errors/e89981268d8452bc. Report an issue: GitHub.