You set a breakpoint, click the line, and the usual solid red dot shows up. You start debugging, and the moment execution should hit that line, nothing happens — the code just keeps running past it. Looking back at the gutter, the dot has changed: it’s now hollow, a grey circle with a hole in the middle instead of a solid one. Nothing crashed, no error appeared, the debugger just quietly decided not to stop there.
That hollow dot is the whole story, and it’s more specific than it looks. VS Code calls this an “unverified” breakpoint, and the name is accurate: it isn’t marking the line as wrong, or your code as broken. It’s telling you that the debugger could not connect this exact line in your editor to any line in the code it’s actually executing right now. A verified breakpoint (solid red) means the debug adapter found a matching, runnable line and is watching it. An unverified one means it looked and found nothing to bind to — so there’s nothing for it to pause on, no matter how many times you run past it.
What’s actually happening
When you start a debug session, VS Code doesn’t debug the file you’re looking at directly — it hands a running process to a debug adapter (one per language/runtime), and that adapter reports back which lines in which files actually correspond to real, loaded, executable code. Your breakpoint only turns solid once the adapter confirms that mapping exists. Until then, or if it never can, the dot stays hollow.
The most common reason that mapping fails is a mismatch between the file you’re editing and the file that’s actually running. For compiled or transpiled languages — TypeScript compiling to JavaScript, or any bundler-based JS project — the runtime executes generated output, not your source file. A source map is what lets the debugger translate “line 42 of your .ts file” into “line 187 of the bundled .js file the browser or Node process is actually running.” If that source map is missing, stale, or points at the wrong output location, the debugger has no way to make that translation — so a breakpoint on your source line has nothing on the runtime side to bind to, and it stays unverified.
The second common cause is simpler: you’re debugging a different copy of the code than the one you’re editing. A dist/ or build/ folder holding yesterday’s compiled output, a Docker container running a stale image, or a launch.json outFiles setting pointing somewhere that no longer matches your source layout — all of these mean the process VS Code attached to isn’t running the code in front of you, even though the file names might look identical.
The fix, step by step
1. Confirm it’s actually unverified, not just unhit. Hover over the hollow dot — VS Code shows a tooltip explaining why, and for source-map issues it’s usually explicit about not being able to find the generated code. That tooltip is worth reading before changing anything; it tells you which of the causes below you’re actually dealing with.
2. For compiled/transpiled code, check that source maps are being generated and referenced correctly. Confirm your build config actually emits .map files ("sourceMap": true in tsconfig.json, or your bundler’s equivalent flag), and that your launch.json has an outFiles entry pointing at where the compiled output actually lands — a mismatch here is the single most common cause of this exact symptom.
3. Confirm you’re running the code you think you’re running. Stop the debug session, delete or rebuild your dist/build output, and restart. If the breakpoint verifies immediately after a clean rebuild, the previous run was executing stale compiled output, not your current source.
4. If you’re attaching to an already-running process (a container, a remote server, a long-running dev server you didn’t start from VS Code), double-check that the path mapping between your local source and the remote source matches exactly — a sourceFileMap or remoteRoot/localRoot setting that’s slightly off will produce exactly this symptom, because the debugger genuinely can’t find your file on the other end.
Two mistakes worth knowing about ahead of time
Assuming a hollow breakpoint means the line is unreachable in your program’s logic. It doesn’t say anything about your control flow — it says the debugger can’t map the line to running code at all. A perfectly reachable, frequently executed line can show as unverified for purely mechanical reasons (a stale build), while a genuinely dead line would show as verified and just never get hit — a different, quieter problem with a different fix.
Restarting the debug session repeatedly without changing anything. If the mapping is broken, restarting the exact same broken setup produces the exact same hollow dot every time. The fix is almost always in the build output or the launch.json configuration, not in the act of starting a new session — check those before assuming it’s a fluke.
A habit that prevents the confusion entirely
Any time you’re debugging code that goes through a build step — TypeScript, a bundler, a transpiler of any kind — treat source maps as part of your build’s correctness, not an afterthought. Confirm they’re being generated on every build, not just the first one, and that your launch.json points at the right output directory before you start hunting for why a breakpoint won’t stop. A hollow dot isn’t VS Code failing to notice your breakpoint — it’s VS Code being honest that, as far as the running process is concerned, that line doesn’t currently exist.