Two places where a breakpoint that will never fire is reported in a way that reads like success. In both, the server already holds the information needed to say otherwise — it just isn't surfaced at the moment the agent would act on it. Same theme as #439 ("the run that worked looking like the one that failed"), one step earlier in the loop.
Case 1 — start_debugging returns state: "stopped" with no hint that a breakpoint didn't bind
The program ran to completion because the breakpoint never bound. Nothing in the response says so — the only signal is state: "stopped" where the agent expected "paused", which requires the agent to already suspect the problem.
The explanation exists and is genuinely excellent — it's just one tool call away, and nothing points there:
That message names the exact fix (a sourceMap entry). An agent that doesn't think to call list_breakpoints after an unexpectedly-stopped launch never sees it.
Suggested fix: when a launch reaches stopped and the session has unverified breakpoints, attach a warning to the start_debugging response summarising them (count + the per-breakpoint message already stored). Cheap, uses existing data, and closes the loop exactly where the agent is looking.
Case 2 — a breakpoint the server knows can never bind is still accepted
The warning is genuinely good — clear, specific, names the correct form. But the call still returns success: true for a breakpoint the server has just stated will never bind, and it then sits in list_breakpoints looking like a live breakpoint. Confirmed: launching with it never stops.
Suggested fix, in rough order of preference:
- Auto-qualify. The server knows the language, the package, and the correct form.
main → main.main is mechanical. Other adapters (.NET Add, Rust calculate_sum) bind bare names fine, so today the agent must carry per-adapter naming trivia that the server already has.
- Return the corrected name in a structured field (e.g.
suggestedName: "main.main") so it's machine-actionable rather than prose an agent has to parse.
- Reject it. If the server is certain it can never bind,
success: false with the same message is more honest than a permanently-dead breakpoint in the list.
(1) and (2) compose well: qualify it, and say that you did.
Why these matter for agent ergonomics
Both are cases where an agent proceeds on a false belief that a breakpoint is live, and no response contradicts it until the debugging session has already gone wrong. The diagnostics are already written and already good — the gap is purely where they surface.
Found during a full /testdebugger sweep (9 servers × 3 backends).
Two places where a breakpoint that will never fire is reported in a way that reads like success. In both, the server already holds the information needed to say otherwise — it just isn't surfaced at the moment the agent would act on it. Same theme as #439 ("the run that worked looking like the one that failed"), one step earlier in the loop.
Case 1 —
start_debuggingreturnsstate: "stopped"with no hint that a breakpoint didn't bindset_breakpoint { file: "examples/rust/hello_world/src/main.rs", line: 27 } → { "success": true, "verified": false } // normal pre-launch start_debugging { scriptPath: "…/target-linux/debug/hello_world" } → { "success": true, "state": "stopped", "data": { "stopOnEntrySuccessful": false } }The program ran to completion because the breakpoint never bound. Nothing in the response says so — the only signal is
state: "stopped"where the agent expected"paused", which requires the agent to already suspect the problem.The explanation exists and is genuinely excellent — it's just one tool call away, and nothing points there:
list_breakpoints { } → { "verified": false, "message": "Breakpoint at /workspace/examples/rust/hello_world/src/main.rs:27 could not be resolved, but a valid location was found at /workspace/rust/hello_world/src/main.rs:27" }That message names the exact fix (a
sourceMapentry). An agent that doesn't think to calllist_breakpointsafter an unexpectedly-stoppedlaunch never sees it.Suggested fix: when a launch reaches
stoppedand the session has unverified breakpoints, attach awarningto thestart_debuggingresponse summarising them (count + the per-breakpointmessagealready stored). Cheap, uses existing data, and closes the loop exactly where the agent is looking.Case 2 — a breakpoint the server knows can never bind is still accepted
set_breakpoint { function: "main" } // go session → { "success": true, "verified": false, "warning": "Go function breakpoints use package-qualified names — for func main in package main use 'main.main'. A bare identifier like 'main' will never bind" }The warning is genuinely good — clear, specific, names the correct form. But the call still returns
success: truefor a breakpoint the server has just stated will never bind, and it then sits inlist_breakpointslooking like a live breakpoint. Confirmed: launching with it never stops.Suggested fix, in rough order of preference:
main→main.mainis mechanical. Other adapters (.NETAdd, Rustcalculate_sum) bind bare names fine, so today the agent must carry per-adapter naming trivia that the server already has.suggestedName: "main.main") so it's machine-actionable rather than prose an agent has to parse.success: falsewith the same message is more honest than a permanently-dead breakpoint in the list.(1) and (2) compose well: qualify it, and say that you did.
Why these matter for agent ergonomics
Both are cases where an agent proceeds on a false belief that a breakpoint is live, and no response contradicts it until the debugging session has already gone wrong. The diagnostics are already written and already good — the gap is purely where they surface.
Found during a full
/testdebuggersweep (9 servers × 3 backends).