Skip to content

Breakpoints not hit

Symptom: the session starts, the program runs to completion, and nothing ever stops.

First: what did the adapter say?

Open the Breakpoints panel — Ctrl+B. Every breakpoint carries a circle:

Circle State Meaning
Filled verified the adapter accepted it — the problem is elsewhere
Yellow ring rejected the adapter refused it
Dimmed ring pending no answer yet

That single glyph splits the problem in half.

The breakpoint was rejected (yellow ring)

The adapter would not bind it. Causes, in order of likelihood:

The line is not executable. Blank lines, comments, declarations, a closing brace. Move to a line that does something.

The code was optimised away. Inlined functions and eliminated branches have no code to stop at. Rebuild without optimisation:

  • Rust: debug build, not --release
  • C / C++: -g -O0
  • Go: keep -gcflags=all=-N -l if you overrode the build

There is no debug information. A binary stripped of symbols, or a dependency compiled without them, cannot host a breakpoint.

The breakpoint is pending (dimmed ring)

No answer has arrived. Whether that is a problem depends on the language:

  • PHP: normal before the first request. The set is visible in the window, but until Xdebug connects there is nobody to ask. Send a request.
  • Everything else: if it stays pending after the session has started, the adapter is not answering — see Adapter fails to start.

Note also that an adapter is allowed to move a breakpoint to a nearby line. When it does, the line you asked for gets no confirmation and stays pending, even though a breakpoint does exist a line or two away.

The breakpoint is verified but never fires

The adapter accepted it, so the question is now about your program, not the debugger.

The code does not run. The most common answer, and the least welcome one. Put a breakpoint on the first line of main, or on the function that calls this one, and work forwards.

A second process is running the code. This one is easy to miss:

  • Django, Flask, uvicorn with an autoreloader fork a worker the debugger is not attached to — add --noreload or the equivalent;
  • a task queue or a worker pool executes the code somewhere else entirely.

The request finished before debugging started (PHP). Xdebug connects at the start of a request; one already in flight cannot be debugged retroactively. Send another.

The PHP test command timed out. For a Docker-based :DebugTest, Bugsaur waits 30 seconds for the test container to connect to Xdebug. If the GUI reports that Docker may be unavailable or hung, check that Docker Desktop/OrbStack is running, then inspect the containers with docker compose ps and try the test command manually. If testCommand exits unsuccessfully, the error also shows the exact command, exit status and captured Docker/Compose output.

The condition is never true. A conditional breakpoint with an expression that never evaluates true behaves exactly like a breakpoint that does not work. Temporarily remove the condition to check.

A test run ignored your saved breakpoints. :DebugTest and --test-at use only the breakpoints of that request — by design. Set the breakpoint in the same session.

An explicit --break replaced the set. --break does not add to the saved set, it replaces it entirely.

It stops, but there are no variables

A different problem with the same root: the frame has no debug information.

Cause Fix
Release / optimised build rebuild in debug
The frame is library or runtime code select one of your own frames in the Call Stack
Rust --release debug build
Go with a custom build keep -gcflags=all=-N -l

Checklist

□ Is the circle filled, yellow or dimmed?
□ Is the line executable?
□ Is the build unoptimised?
□ Does the code actually run — does a breakpoint in main fire?
□ Is there a second process (autoreloader, worker)?
□ Is a condition attached that is never true?
□ Is this a test run, which ignores saved breakpoints?
□ Did --break replace the saved set?