{"record":{"id":"2bb515b37d0f5e4f","repo":"go-delve/delve","slug":"thread-d-does-not-exist","errorCode":null,"errorMessage":"thread %d does not exist","messagePattern":"thread (.+?) does not exist","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/proc/target.go","lineNumber":352,"sourceCode":"\t}\n\tif g.Thread != nil {\n\t\treturn t.SwitchThread(g.Thread.ThreadID())\n\t}\n\tt.selectedGoroutine = g\n\treturn nil\n}\n\n// SwitchThread will change the selected and active thread.\nfunc (t *Target) SwitchThread(tid int) error {\n\tif ok, err := t.Valid(); !ok {\n\t\treturn err\n\t}\n\tif th, ok := t.FindThread(tid); ok {\n\t\tt.currentThread = th\n\t\tt.selectedGoroutine, _ = GetG(t.CurrentThread())\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"thread %d does not exist\", tid)\n}\n\n// setAsyncPreemptOff enables or disables async goroutine preemption by\n// writing the value 'v' to runtime.debug.asyncpreemptoff.\n// A value of '1' means off, a value of '0' means on.\nfunc setAsyncPreemptOff(p *Target, v int64) {\n\tif producer := p.BinInfo().Producer(); producer == \"\" || !goversion.ProducerAfterOrEqual(producer, 1, 14) {\n\t\treturn\n\t}\n\tlogger := p.BinInfo().logger\n\tscope := globalScope(p, p.BinInfo(), p.BinInfo().Images[0], p.Memory())\n\t// +rtype -var debug anytype\n\tdebugv, err := scope.findGlobal(\"runtime\", \"debug\")\n\tif err != nil {\n\t\tlogger.Warnf(\"could not find runtime/debug variable (or unreadable): %v\", err)\n\t\treturn\n\t}\n\tif debugv.Unreadable != nil {","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/go-delve/delve/blob/a23773e6c31361e43246bc43a424ee009679b174/pkg/proc/target.go#L334-L370","documentation":"Target.SwitchThread was asked to make thread `tid` the current thread, but no thread with that ID exists in the target process. Delve only allows switching to threads it is actively tracking; when a thread has exited (or never existed) it refuses the switch and returns this error.","triggerScenarios":"Calling SwitchThread (directly or via SwitchGoroutine, setCurrentThreads, pickCurrentThread, or RPC2/terminal 'thread <id>' / 'goroutine <id>') with a tid that is not in the target's current thread list — typically because the OS thread exited after the caller obtained the ID.","commonSituations":"Stale thread IDs captured before the debuggee exited threads (common with Go's M exiting); scripting the terminal `threads`/`thread` commands with outdated IDs; attaching/detaching races; picking a thread of a child process that has been reaped after an exec/fork follow.","solutions":["Re-list threads with `threads` (or rpc2 ListThreads) and switch to a currently existing tid","Re-select the goroutine instead of a raw thread (`goroutine <id>`) so Delve resolves a valid current thread","Refresh debugger state after continue/step — old thread IDs are invalidated as threads exit","If using gdbserial/core backends, ensure the thread belongs to the currently focused target in a multi-process (follow-exec) session"],"exampleFix":"// before\n// stale id from an earlier list\nerr := tgt.SwitchThread(12345) // thread exited since\n// after\nthreads, _ := tgt.ThreadList()\nfor _, th := range threads {\n    if th.ThreadID() == tid {\n        err = tgt.SwitchThread(tid)\n        break\n    }\n}","handlingStrategy":"validation","validationCode":"if _, ok := tgt.FindThread(tid); !ok {\n    // refresh thread list before switching\n    threads, _ := tgt.ThreadList()\n    // pick a valid tid or abort\n    return\n}\nerr := tgt.SwitchThread(tid)","typeGuard":"func threadExists(t *Target, tid int) bool {\n    _, ok := t.FindThread(tid)\n    return ok\n}","tryCatchPattern":"if err := dbg.SwitchGoroutine(false, gid); err != nil {\n    if strings.Contains(err.Error(), \"does not exist\") {\n        // refresh state and retry with a current id\n        state, _ := dbg.State(false)\n        _ = dbg.SwitchGoroutine(false, state.SelectedGoroutine.ID)\n    }\n}","preventionTips":["Always re-list threads/goroutines after each continue/step before switching","Prefer switching by goroutine over raw thread IDs","Never cache thread IDs across stop events — Go Ms exit frequently","In follow-exec sessions, ensure the ID belongs to the currently focused target"],"tags":["delve","debugger","thread-management","stale-id","process-control"],"backgroundTag":"thread-does-not-exist","analyzedSha":"a23773e6c31361e43246bc43a424ee009679b174","analyzedAt":"2026-08-31T15:12:45.221Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}