go-delve/delve · error

Frame %d does not exist in goroutine %d

Error message

Frame %d does not exist in goroutine %d

What it means

ConvertEvalScope builds an EvalScope for a given goroutine ID and stack frame. After walking the goroutine's stack it checks whether the requested frame index actually exists; if the stack has fewer frames than the requested index, it refuses to build a scope and returns this error. It is a guard against out-of-range frame access, not a debugger malfunction.

Source

Thrown at pkg/proc/eval.go:115

	if deferCall > 0 {
		opts = StacktraceReadDefers
	}

	var locs []Stackframe
	if g != nil {
		if g.Thread != nil {
			threadID = g.Thread.ThreadID()
		}
		locs, err = GoroutineStacktrace(dbp, g, frame+1, opts)
	} else {
		locs, err = ThreadStacktrace(dbp, ct, frame+1)
	}
	if err != nil {
		return nil, err
	}

	if frame >= len(locs) {
		return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
	}

	if deferCall > 0 {
		if deferCall-1 >= len(locs[frame].Defers) {
			return nil, fmt.Errorf("Frame %d only has %d deferred calls", frame, len(locs[frame].Defers))
		}

		d := locs[frame].Defers[deferCall-1]
		if d.Unreadable != nil {
			return nil, d.Unreadable
		}

		return d.EvalScope(dbp, ct)
	}

	return FrameToScope(dbp, dbp.Memory(), g, threadID, locs[frame:]...), nil
}

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Re-fetch the goroutine's stack (Stacktrace/GoroutineStacktrace) and use a frame index within len(locs)-1 before evaluating.
  2. Re-acquire the current execution state: get a fresh stack after every stop event instead of reusing a stale frame index.
  3. Check the goroutine still exists and is stopped (FindGoroutine/valid target) before evaluating in its frames.
  4. For deferred-call scopes, pass deferCall > 0 only when the frame actually has pending defers.

Example fix

// before
scope, err := debugger.ConvertEvalScope(t, gid, 10, 0)
// after
locs, err := proc.GoroutineStacktrace(t, g, 11, 0)
if err != nil { return err }
frame := 10
if frame >= len(locs) { frame = len(locs) - 1 }
scope, err := debugger.ConvertEvalScope(t, gid, frame, 0)
Defensive patterns

Strategy: validation

Validate before calling

locs, err := proc.GoroutineStacktrace(t, g, frame+1, 0)
if err != nil { return err }
if frame < 0 || frame >= len(locs) {
    return fmt.Errorf("frame %d unavailable, goroutine %d has %d frames", frame, gid, len(locs))
}

Type guard

func frameExists(locs []proc.Stackframe, frame int) bool { return frame >= 0 && frame < len(locs) }

Try / catch

scope, err := proc.ConvertEvalScope(t, gid, frame, 0)
if err != nil {
    if strings.Contains(err.Error(), "does not exist in goroutine") {
        frame = 0 // fall back to topmost frame
        scope, err = proc.ConvertEvalScope(t, gid, frame, 0)
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: Calling ConvertEvalScope (directly or via pushLocal from service-level eval APIs, e.g. EvalExpression in a specific frame) with a frame index >= the number of frames in the target goroutine's stack; e.g. frame 5 on a goroutine whose stack only has 3 frames, or referencing a frame after the goroutine has exited and its stack shrunk.

Common situations: IDE/plugins caching a stale frame index after a step or breakpoint hit changed the stack depth; clients storing frame numbers across continuation events; goroutine that finished between listing and evaluation; asking for a deferred call frame on a shallow goroutine stack.

Related errors


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