go-delve/delve · error

error waiting for thread stop %d

Error message

error waiting for thread stop %d

What it means

waitForStop repeatedly asks all threads of the mach task to stop and consults num_running_threads to check progress. A negative return means the mach thread-count query itself failed (invalid task or thread error), so Delve aborts with "error waiting for thread stop <n>" where n is the negative mach code.

Source

Thrown at pkg/proc/native/proc_darwin.go:396

		return th, nil
	}
}

func (dbp *nativeProcess) waitForStop() ([]int, error) {
	ports := make([]int, 0, len(dbp.threads))
	count := 0
	for {
		var task C.task_t
		port := C.mach_port_wait(dbp.os.portSet, &task, C.int(1))
		if port != 0 && port != dbp.os.notificationPort && port != C.MACH_RCV_INTERRUPTED {
			count = 0
			ports = append(ports, int(port))
		} else {
			n := C.num_running_threads(dbp.os.task)
			if n == 0 {
				return ports, nil
			} else if n < 0 {
				return nil, fmt.Errorf("error waiting for thread stop %d", n)
			} else if count > 16 {
				return nil, fmt.Errorf("could not stop process %d", n)
			}
		}
	}
}

func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) {
	var status sys.WaitStatus
	wpid, err := sys.Wait4(pid, &status, options, nil)
	return wpid, &status, err
}

func killProcess(pid int) error {
	return sys.Kill(pid, sys.SIGINT)
}

func (dbp *nativeProcess) exitGuard(err error) error {

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Confirm the process is still alive; if it exited, restart the debug session.
  2. Retry the stop — the exit notification may have raced with the request.
  3. Check the printed negative code to distinguish task-invalid from thread errors.
  4. Update macOS/Delve if it recurs with long-running programs (task port validity handling).

Example fix

// before
(dlv) halt   // issued right as program exits
// after
(dlv) goroutines  // verify target still alive before halting
(dlv) halt
Defensive patterns

Strategy: retry

Validate before calling

// Confirm the debuggee is still alive before issuing a stop:
out, err := exec.Command("ps", "-p", strconv.Itoa(dbp.pid)).Output()
if err != nil || len(out) == 0 {
    return fmt.Errorf("inferior %d exited; cannot stop", dbp.pid)
}

Try / catch

ports, err := dbp.waitForStop(dbp)
if err != nil && strings.Contains(err.Error(), "error waiting for thread stop") {
    // negative code printed: likely task died mid-stop; check process state and retry once
}

Prevention

When it happens

Trigger: Calling stop (e.g. halt request or before a memory write) on macOS while the inferior task becomes invalid mid-loop — the process exited or was killed during thread enumeration, making C.num_running_threads return < 0.

Common situations: Halt issued just as the inferior crashes or exits; debugging a short-lived process; race between an exit notification and the stop loop.

Related errors


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