go-delve/delve · error
thread %d does not exist
Error message
thread %d does not exist
What it means
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.
Source
Thrown at pkg/proc/target.go:352
}
if g.Thread != nil {
return t.SwitchThread(g.Thread.ThreadID())
}
t.selectedGoroutine = g
return nil
}
// SwitchThread will change the selected and active thread.
func (t *Target) SwitchThread(tid int) error {
if ok, err := t.Valid(); !ok {
return err
}
if th, ok := t.FindThread(tid); ok {
t.currentThread = th
t.selectedGoroutine, _ = GetG(t.CurrentThread())
return nil
}
return fmt.Errorf("thread %d does not exist", tid)
}
// setAsyncPreemptOff enables or disables async goroutine preemption by
// writing the value 'v' to runtime.debug.asyncpreemptoff.
// A value of '1' means off, a value of '0' means on.
func setAsyncPreemptOff(p *Target, v int64) {
if producer := p.BinInfo().Producer(); producer == "" || !goversion.ProducerAfterOrEqual(producer, 1, 14) {
return
}
logger := p.BinInfo().logger
scope := globalScope(p, p.BinInfo(), p.BinInfo().Images[0], p.Memory())
// +rtype -var debug anytype
debugv, err := scope.findGlobal("runtime", "debug")
if err != nil {
logger.Warnf("could not find runtime/debug variable (or unreadable): %v", err)
return
}
if debugv.Unreadable != nil {View on GitHub (pinned to a23773e6c3)
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
Example fix
// before
// stale id from an earlier list
err := tgt.SwitchThread(12345) // thread exited since
// after
threads, _ := tgt.ThreadList()
for _, th := range threads {
if th.ThreadID() == tid {
err = tgt.SwitchThread(tid)
break
}
} Defensive patterns
Strategy: validation
Validate before calling
if _, ok := tgt.FindThread(tid); !ok {
// refresh thread list before switching
threads, _ := tgt.ThreadList()
// pick a valid tid or abort
return
}
err := tgt.SwitchThread(tid) Type guard
func threadExists(t *Target, tid int) bool {
_, ok := t.FindThread(tid)
return ok
} Try / catch
if err := dbg.SwitchGoroutine(false, gid); err != nil {
if strings.Contains(err.Error(), "does not exist") {
// refresh state and retry with a current id
state, _ := dbg.State(false)
_ = dbg.SwitchGoroutine(false, state.SelectedGoroutine.ID)
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- operator %s not supported
- could not dereference %s: %v
- could not dereference %s (no children?)
- could not read context: %v
- hardware breakpoints exhausted
AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31).
Data as JSON: /api/errors/2bb515b37d0f5e4f.
Report an issue: GitHub.