go-delve/delve · error

could not set options for new traced thread %d %s

Error message

could not set options for new traced thread %d %s

What it means

Delve attached to a new thread and consumed its initial stop, but the second PtraceSetOptions call (after the ESRCH recovery wait) failed with an error other than ESRCH. Without these options (PTRACE_O_TRACECLONE and optionally exec/fork tracing) the debugger cannot automatically track child threads, breaking thread and process follow behavior.

Source

Thrown at pkg/proc/native/proc_linux.go:383

		if err != nil {
			return nil, err
		}
		if status.Exited() {
			return nil, fmt.Errorf("thread already exited %d", pid)
		}
	}

	dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) })
	if err == syscall.ESRCH {
		if _, _, err = dbp.waitFast(tid); err != nil {
			return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err)
		}
		dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) })
		if err == syscall.ESRCH {
			return nil, err
		}
		if err != nil {
			return nil, fmt.Errorf("could not set options for new traced thread %d %s", tid, err)
		}
	}

	dbp.threads[tid] = &nativeThread{
		ID:  tid,
		dbp: dbp,
		os:  new(osSpecificDetails),
	}
	if dbp.memthread == nil {
		dbp.memthread = dbp.threads[tid]
	}
	for _, bp := range dbp.Breakpoints().M {
		if bp.WatchType != 0 {
			err := dbp.threads[tid].writeHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
			if err != nil {
				return nil, err
			}
		}

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Run the debugger with elevated privileges (root or CAP_SYS_PTRACE) and confirm the container seccomp profile allows ptrace (Docker: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined).
  2. Check kernel compatibility: gVisor/Kata containers may not support all PTRACE_O_* options; run on a standard kernel.
  3. Verify the thread's state via /proc/<pid>/task/<tid>/stat — threads stuck in D state can't respond to ptrace requests until they return to the run queue.
  4. Retry the debugger session; transient states usually clear.
Defensive patterns

Strategy: validation

Validate before calling

// check the environment supports ptrace options before launching
func ptraceEnvOK() error {
    if os.Getenv("KUBERNETES_SERVICE_HOST") != "" {
        if _, err := os.Stat("/proc/sys/kernel/yama/ptrace_scope"); err != nil {
            return fmt.Errorf("check container seccomp profile allows ptrace")
        }
    }
    if os.Geteuid() != 0 {
        return fmt.Errorf("run with root or CAP_SYS_PTRACE for full ptrace option support")
    }
    return nil
}

Type guard

func isSetOptionsErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "could not set options for new traced thread")
}

Try / catch

if err := startDebug(); err != nil {
    if isSetOptionsErr(err) {
        return fmt.Errorf("%w; ensure container has SYS_PTRACE and default seccomp profile is not blocking ptrace", err)
    }
    return err
}

Prevention

When it happens

Trigger: addThread: after the ESRCH-retry path, syscall.PtraceSetOptions(tid, ptraceOptions) returns a non-ESRCH, non-nil error — e.g. EINVAL from invalid option combination on the kernel, EPERM from security restrictions, or EIO if the thread is in an unexpected state.

Common situations: Running on kernels/containers that restrict ptrace options (seccomp filters blocking ptrace); attaching to processes in uninterruptible states (D state) causing EIO; sandboxed CI environments (gVisor, some Docker seccomp profiles) with incomplete ptrace support.

Related errors


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