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
- Make delve.catch the entire top-level expression and move surrounding logic inside its argument or into the called function
- Wrap the comparison itself: `delve.catch(x == f())` instead of `x == delve.catch(f())`
- 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
- Remember delve.catch must wrap the ENTIRE condition expression
- Never nest delve.catch inside arithmetic, comparisons, or other calls
- Read it as a statement-level wrapper, not a normal function
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
- wrong number of arguments for %s.%s
- can not convert value of type %s to int
- can not convert value of type %s to uint
- %s has no member %s
- %s (type %s) has no member %s
AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31).
Data as JSON: /api/errors/fe924e10fef5d1b1.
Report an issue: GitHub.