go-delve/delve · error

%s.%s can only be used at toplevel

Error message

%s.%s can only be used at toplevel

What it means

Delve provides a special `delve.catch(...)` expression usable in breakpoint conditions to suppress errors from function-call injection. It is only valid as a whole top-level expression; the compiler rejects it when it appears nested inside a larger expression (e.g. as an operand), because error suppression cannot be scoped to a sub-expression.

Source

Thrown at pkg/proc/evalop/evalcompile.go:300

		if err != nil {
			return err
		}
	}

	if depth[len(ctx.ops)] != endDepth {
		return fmt.Errorf("internal debugger error: depth check failed: depth at the end is not %d (got %d)\n%s", depth[len(ctx.ops)], endDepth, Listing(depth, ctx.ops))
	}
	return nil
}

func (ctx *compileCtx) compileAST(t ast.Expr, toplevel bool) error {
	switch node := t.(type) {
	case *ast.CallExpr:
		if f, ok := node.Fun.(*ast.SelectorExpr); ok && ctx.flags&BreakpointCondition != 0 {
			const catch = "catch"
			if ident, ok := f.X.(*ast.Ident); ok && ident.Name == DelvePackage && f.Sel.Name == catch {
				if !toplevel {
					return fmt.Errorf("%s.%s can only be used at toplevel", DelvePackage, catch)
				}
				if len(node.Args) != 1 {
					return fmt.Errorf("wrong number of arguments for %s.%s", DelvePackage, catch)
				}
				ctx.pushOp(&DisableErrors{})
				return ctx.compileAST(node.Args[0], false)
			}
		}
		return ctx.compileTypeCastOrFuncCall(node, toplevel)

	case *ast.Ident:
		return ctx.compileIdent(node)

	case *ast.ParenExpr:
		// otherwise just eval recursively
		return ctx.compileAST(node.X, false)

	case *ast.SelectorExpr: // <expression>.<identifier>

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Make delve.catch the entire top-level expression and move surrounding logic inside its argument or into the called function
  2. Wrap the comparison itself: `delve.catch(x == f())` instead of `x == delve.catch(f())`
  3. Do the arithmetic/comparison inside a helper function that delve.catch then calls

Example fix

// before (nested -> rejected)
condition 1 x == delve.catch(f())
// after (toplevel)
condition 1 delve.catch(x == f())
Defensive patterns

Strategy: validation

Validate before calling

// Validate the condition shape before setting it:
func isToplevelCatch(cond string) bool {
    expr, err := parser.ParseExpr(cond)
    if err != nil { return false }
    call, ok := expr.(*ast.CallExpr)
    if !ok { return false }
    sel, ok := call.Fun.(*ast.SelectorExpr)
    id, _ := sel.X.(*ast.Ident)
    return ok && id != nil && id.Name == "delve" && sel.Sel.Name == "catch"
}

Try / catch

if err := setBreakpointCondition(bp, cond); err != nil &&
    strings.Contains(err.Error(), "can only be used at toplevel") {
    // rewrite condition: move outer logic inside delve.catch(...)
    return setBreakpointCondition(bp, "delve.catch("+inner+")")
}

Prevention

When it happens

Trigger: Compiling a breakpoint condition or eval expression where a `delve.catch` call appears anywhere other than the top level: `x == delve.catch(f())`, `delve.catch(f()) + 1`, inside parens of another expression, or inside another call's argument list.

Common situations: Users write arithmetic or comparisons around delve.catch expecting it to behave like a normal function; conditions copied from examples get combined with other predicates.

Related errors


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