go-delve/delve · critical
wait err %s %d
Error message
wait err %s %d
What it means
The wait() system call invoked by trapWaitInternal returned an error while Delve was waiting for a tracee to stop, exit, or signal. trapWait is the core event loop of the native Linux backend: every breakpoint hit, thread exit, and manual stop flows through it, so any wait failure (EINTR races aside) indicates the debugger lost sync with its tracees.
Source
Thrown at pkg/proc/native/proc_linux.go:456
)
func trapWaitInternal(procgrp *processGroup, pid int, options trapWaitOptions) (*nativeThread, error) {
var waitdbp *nativeProcess = nil
if len(procgrp.procs) == 1 {
// Note that waitdbp is only used to call (*nativeProcess).wait which will
// behave correctly if waitdbp == nil.
waitdbp = procgrp.procs[0]
}
halt := options&trapWaitHalt != 0
for {
wopt := 0
if options&trapWaitNohang != 0 {
wopt = sys.WNOHANG
}
wpid, status, err := waitdbp.wait(pid, wopt)
if err != nil {
return nil, fmt.Errorf("wait err %s %d", err, pid)
}
if wpid == 0 {
if options&trapWaitNohang != 0 {
return nil, nil
}
continue
}
dbp := procgrp.procForThread(wpid)
var th *nativeThread
if dbp != nil {
var ok bool
th, ok = dbp.threads[wpid]
if ok {
th.Status = (*waitStatus)(status)
}
} else {
dbp = procgrp.procs[0]
}View on GitHub (pinned to a23773e6c3)
Solutions
- Check whether the debuggee already exited (dlv will usually report ErrProcessExited on the next command); reconnect or restart the debug session.
- Retry the wait: transient EINTR errors usually resolve on the next call.
- Ensure only one tracer is attached — other tools (strace, another dlv) reaping tracees cause ECHILD.
- If it reproduces in containers, run with --init (proper child reaping, e.g. tini) and PID 1 semantics.
- Upgrade Delve: several wait-loop robustness fixes (EINTR handling) have landed over time.
Defensive patterns
Strategy: try-catch
Validate before calling
// before waiting, confirm the process is still traceable and not reaped
func stillTraced(pid int) error {
b, err := os.ReadFile(fmt.Sprintf("/proc/%d/status", pid))
if err != nil {
return fmt.Errorf("process %d gone: %w", pid, err)
}
if !strings.Contains(string(b), "State:\tT (stopped)") && !strings.Contains(string(b), "t (tracing stop)") {
// state may have changed; not fatal, but log it
return nil
}
return nil
} Type guard
func isWaitErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "wait err ")
} Try / catch
th, err := trapWait(procgrp, pid)
if err != nil {
var exited proc.ErrProcessExited
if errors.As(err, &exited) {
// debuggee died: clean up and end session gracefully
detachAndCleanup()
return fmt.Errorf("debuggee exited with status %d", exited.Status)
}
if isWaitErr(err) && errors.Is(syscall.ECHILD, err) {
return fmt.Errorf("tracee reaped by another process; check for concurrent strace/dlv")
}
return err
} Prevention
- Attach to processes you can monitor — a target dying mid-session is the most common cause
- Never run two tracers (dlv, strace, gdb) on the same process simultaneously
- In containers use an init process (tini, docker --init) for correct child reaping
- Handle ErrProcessExited before interpreting wait errors
- Keep Delve up to date for wait-loop EINTR/robustness fixes
When it happens
Trigger: trapWait / trapWaitNohang (via stop or exitGuard): waitdbp.wait(pid, wopt) returns an error — typically ECHILD (all children reaped / process already exited and was collected elsewhere), EINTR propagated without retry, or EINVAL from an options/pid mismatch in __waitpid.
Common situations: The debugged process exited concurrently (dlv attach to a process that dies mid-session) yielding ECHILD; signal storms causing repeated EINTR; running under environment wrappers (IDE launches, test harnesses) that interfere with child reaping; multi-process follow-exec sessions where a child was reaped by another thread of the debugger.
Related errors
- error while waiting after adding thread: %d %s
- could not connect
- no eBPF program loaded
- eBPF map not loaded
- number of loaded libraries exceeds maximum
AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31).
Data as JSON: /api/errors/3e0db8dffdc5cdd5.
Report an issue: GitHub.