{"record":{"id":"05be62834a631083","repo":"go-delve/delve","slug":"could-not-attach-to-new-thread-d-s","errorCode":null,"errorMessage":"could not attach to new thread %d %s","messagePattern":"could not attach to new thread (.+?) (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/proc/native/proc_linux.go","lineNumber":362,"sourceCode":"func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) {\n\tif thread, ok := dbp.threads[tid]; ok {\n\t\treturn thread, nil\n\t}\n\n\tptraceOptions := ptraceOptionsNormal\n\tif dbp.followExec {\n\t\tptraceOptions = ptraceOptionsFollowExec\n\t}\n\n\tvar err error\n\tif attach {\n\t\tdbp.execPtraceFunc(func() { err = sys.PtraceAttach(tid) })\n\t\tif err != nil && err != sys.EPERM {\n\t\t\t// Do not return err if err == EPERM,\n\t\t\t// we may already be tracing this thread due to\n\t\t\t// PTRACE_O_TRACECLONE. We will surely blow up later\n\t\t\t// if we truly don't have permissions.\n\t\t\treturn nil, fmt.Errorf(\"could not attach to new thread %d %s\", tid, err)\n\t\t}\n\t\tpid, status, err := dbp.waitFast(tid)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif status.Exited() {\n\t\t\treturn nil, fmt.Errorf(\"thread already exited %d\", pid)\n\t\t}\n\t}\n\n\tdbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) })\n\tif err == syscall.ESRCH {\n\t\tif _, _, err = dbp.waitFast(tid); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error while waiting after adding thread: %d %s\", tid, err)\n\t\t}\n\t\tdbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) })\n\t\tif err == syscall.ESRCH {\n\t\t\treturn nil, err","sourceCodeStart":344,"sourceCodeEnd":380,"githubUrl":"https://github.com/go-delve/delve/blob/a23773e6c31361e43246bc43a424ee009679b174/pkg/proc/native/proc_linux.go#L344-L380","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: attach to every TID seen in /proc, failing on race\nfor _, tidpath := range tids {\n    if _, err := dbp.addThread(tid, tid != dbp.pid); err != nil {\n        return err // aborts whole update on one dead thread\n    }\n}\n// after: tolerate threads that vanished mid-scan\nfor _, tidpath := range tids {\n    if _, err := dbp.addThread(tid, tid != dbp.pid); err != nil {\n        if _, ok := err.(proc.ErrThreadExited); ok {\n            continue\n        }\n        return err\n    }\n}","handlingStrategy":"retry","validationCode":"// before attaching, verify permissions and Yama policy\nfunc canAttach(pid int) error {\n    if err := syscall.Kill(pid, 0); err != nil {\n        return fmt.Errorf(\"process %d not accessible: %w\", pid, err)\n    }\n    yama, err := os.ReadFile(\"/proc/sys/kernel/yama/ptrace_scope\")\n    if err == nil && strings.TrimSpace(string(yama)) != \"0\" && os.Geteuid() != 0 {\n        return fmt.Errorf(\"yama ptrace_scope restricts attach; run as root or CAP_SYS_PTRACE\")\n    }\n    return nil\n}","typeGuard":"func isAttachDenied(err error) bool {\n    return err != nil && (errors.Is(err, syscall.EPERM) || strings.Contains(err.Error(), \"could not attach to new thread\"))\n}","tryCatchPattern":"th, err := updateThreadList()\nif err != nil {\n    if isAttachDenied(err) {\n        // surface actionable hint: sudo / CAP_SYS_PTRACE / yama scope\n        return fmt.Errorf(\"attach failed: %w (try: sudo, or sysctl kernel.yama.ptrace_scope=0)\", err)\n    }\n    return err\n}","preventionTips":["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"],"tags":["linux","ptrace","process-attach","threading"],"backgroundTag":"ptrace-attach-failed","analyzedSha":"a23773e6c31361e43246bc43a424ee009679b174","analyzedAt":"2026-08-31T15:12:45.221Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}