go-delve/delve · error · couldNotGetThreadCount

could not get thread count

Error message

could not get thread count

What it means

This sentinel error is returned when the Mach `thread_count` call fails (returns -1) while Delve is enumerating the threads of a debugged task on macOS. It means Delve could not determine how many threads exist in the target process, so thread state cannot be synced. It is produced in updateThreadListForTask and surfaces through Launch and updateThreadList.

Source

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

	dbp.postExit()
	return
}

func (dbp *nativeProcess) requestManualStop() (err error) {
	var (
		task          = C.mach_port_t(dbp.os.task)
		thread        = C.mach_port_t(dbp.memthread.os.threadAct)
		exceptionPort = C.mach_port_t(dbp.os.exceptionPort)
	)
	dbp.os.halt = true
	kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT)
	if kret != C.KERN_SUCCESS {
		return fmt.Errorf("could not raise mach exception")
	}
	return nil
}

var couldNotGetThreadCount = errors.New("could not get thread count")
var couldNotGetThreadList = errors.New("could not get thread list")

func (dbp *nativeProcess) updateThreadList() error {
	return dbp.updateThreadListForTask(dbp.os.task)
}

func (dbp *nativeProcess) updateThreadListForTask(task C.task_t) error {
	var (
		err   error
		kret  C.kern_return_t
		count C.int
		list  []uint32
	)

	for {
		count = C.thread_count(task)
		if count == -1 {
			return couldNotGetThreadCount

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Re-run the launch/attach; the failure is often transient due to a race with process exit.
  2. Verify the target process is still alive before launching/attaching (e.g. `ps -p <pid>`).
  3. On newer macOS, ensure delve is codesigned with the get-task-allow entitlement or run on a machine with debugging permitted (SIP/AMFI settings).
  4. Retry after disabling hardened runtime restrictions: `codesign --remove-signature <binary>` for dev builds.
  5. Update delve; Mach task handling is periodically fixed in newer versions.
Defensive patterns

Strategy: try-catch

Validate before calling

// before launching/attaching, ensure the pid is alive
if _, err := os.FindProcess(pid); err != nil {
	return fmt.Errorf("target not running: %w", err)
}

Type guard

// sentinel check
func isCouldNotGetThreadCount(err error) bool {
	return errors.Is(err, errors.New("could not get thread count")) || err != nil && err.Error() == "could not get thread count"
}

Try / catch

if err := dlv.Launch(...); err != nil {
	if err.Error() == "could not get thread count" {
		// process likely exited; retry with a fresh launch
		time.Sleep(100 * time.Millisecond)
		return dlv.Launch(...)
	}
	return err
}

Prevention

When it happens

Trigger: Calling Launch on macOS (or any operation that triggers dbp.updateThreadList) when the underlying Mach task port is invalid or the task has exited, so C.thread_count(task) returns -1.

Common situations: Debugging a target that died or exited between launch and the thread-list refresh; attaching/launching under a hardened-runtime/signature configuration where Mach task ports are restricted; stale task port after process termination.

Related errors


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