go-delve/delve · error
could not attach to new thread %d %s
Error message
could not attach to new thread %d %s
What it means
Delve's native Linux backend fails to ptrace-attach to a newly discovered thread (TID) while adding it to the process's thread list. PTRACE_ATTACH returned a non-EPERM error (EPERM is tolerated because PTRACE_O_TRACECLONE may already be tracing the thread). This means the debugger genuinely cannot gain control of the thread, typically due to a race with thread exit or ptrace restrictions.
Source
Thrown at pkg/proc/native/proc_linux.go:362
func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) {
if thread, ok := dbp.threads[tid]; ok {
return thread, nil
}
ptraceOptions := ptraceOptionsNormal
if dbp.followExec {
ptraceOptions = ptraceOptionsFollowExec
}
var err error
if attach {
dbp.execPtraceFunc(func() { err = sys.PtraceAttach(tid) })
if err != nil && err != sys.EPERM {
// Do not return err if err == EPERM,
// we may already be tracing this thread due to
// PTRACE_O_TRACECLONE. We will surely blow up later
// if we truly don't have permissions.
return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err)
}
pid, status, err := dbp.waitFast(tid)
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, errView on GitHub (pinned to a23773e6c3)
Solutions
- Run the debugger as root or with CAP_SYS_PTRACE (e.g. sudo dlv attach <pid>, or setsetcap cap_sys_ptrace on the dlv binary) to eliminate permission-based failures.
- Check kernel.yama.ptrace_scope (sysctl kernel.yama.ptrace_scope=0) if Yama LSM is blocking attach to unrelated processes.
- Retry the attach: a transient ESRCH usually means the thread exited; a subsequent updateThreadList will pick up live threads.
- Verify the target process is not already being traced by another debugger (can only have one tracer per process).
- Ensure the target binary's ptrace_seize protection (/proc/<pid>/dumpable) is not reset, e.g. after credential changes.
Example fix
// before: attach to every TID seen in /proc, failing on race
for _, tidpath := range tids {
if _, err := dbp.addThread(tid, tid != dbp.pid); err != nil {
return err // aborts whole update on one dead thread
}
}
// after: tolerate threads that vanished mid-scan
for _, tidpath := range tids {
if _, err := dbp.addThread(tid, tid != dbp.pid); err != nil {
if _, ok := err.(proc.ErrThreadExited); ok {
continue
}
return err
}
} Defensive patterns
Strategy: retry
Validate before calling
// before attaching, verify permissions and Yama policy
func canAttach(pid int) error {
if err := syscall.Kill(pid, 0); err != nil {
return fmt.Errorf("process %d not accessible: %w", pid, err)
}
yama, err := os.ReadFile("/proc/sys/kernel/yama/ptrace_scope")
if err == nil && strings.TrimSpace(string(yama)) != "0" && os.Geteuid() != 0 {
return fmt.Errorf("yama ptrace_scope restricts attach; run as root or CAP_SYS_PTRACE")
}
return nil
} Type guard
func isAttachDenied(err error) bool {
return err != nil && (errors.Is(err, syscall.EPERM) || strings.Contains(err.Error(), "could not attach to new thread"))
} Try / catch
th, err := updateThreadList()
if err != nil {
if isAttachDenied(err) {
// surface actionable hint: sudo / CAP_SYS_PTRACE / yama scope
return fmt.Errorf("attach failed: %w (try: sudo, or sysctl kernel.yama.ptrace_scope=0)", err)
}
return err
} Prevention
- Run dlv as root or grant CAP_SYS_PTRACE when attaching to processes you don't own
- Check /proc/sys/kernel/yama/ptrace_scope before attaching on Ubuntu/Debian kernels
- Don't attach to processes already traced by another debugger
- Retry thread-list updates instead of failing the whole session on one bad TID
When it happens
Trigger: Called from addThread(tid, attach=true), e.g. during updateThreadList when enumerating /proc/<pid>/task/*, when sys.PtraceAttach(tid) returns an error other than EPERM — most commonly ESRCH because the thread exited between the /proc glob and the attach, or EPERM blocked by Yama ptrace_scope (only if not already traced), or EINVAL from an invalid TID.
Common situations: Attaching (dlv attach) to a multithreaded process whose threads are churning/exiting rapidly; running inside containers or hardened kernels with kernel.yama.ptrace_scope=2/3 that block attach to non-child processes; trying to attach to a process owned by another user without root/CAP_SYS_PTRACE; stale TIDs read from /proc that died before attach.
Related errors
- thread already exited %d
- process must be stopped in order to kill it
- waiting for target execve failed: %s
- error while waiting after adding thread: %d %s
- could not set options for new traced thread %d %s
AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31).
Data as JSON: /api/errors/05be62834a631083.
Report an issue: GitHub.