Skip to content

Troubleshooting

Find your symptom, apply the fix. Start with lazydap doctor --format json — it checks the things that are usually wrong.

Terminal window
$ lazydap doctor --format json
{
"checks": [
{ "detail": "none; create /Users/you/.config/lazydap/config.toml to add one", "name": "config.file", "ok": true },
{ "detail": "/Users/you/.local/bin/codelldb", "name": "adapter.codelldb", "ok": true },
{ "detail": "/opt/homebrew/bin/python3", "name": "adapter.debugpy", "ok": true },
{ "detail": "no delve binary found on PATH — install it with `go install github.com/go-delve/delve/cmd/dlv@latest`", "name": "adapter.delve", "ok": false },
{ "detail": "/Users/you/lazydap-demo/.lazydap/state.toml (not created yet)", "name": "state.file", "ok": true },
{ "detail": "instance lazydap-demo-13cc8efcde46, pid 12102, protocol v10", "name": "daemon", "ok": true }
],
"ok": true
}

An adapter you have not installed is reported and does not fail the run: "ok": true means lazydap can debug something here. If the daemon itself will not start, doctor says so as a failed daemon check rather than refusing to run — the checks above it have usually already named the reason.

The program runs to completion and hit_breakpoint_ids is empty.

Check verified first. It is in launch’s reply and in breakpoint_updates on any --wait reply:

Terminal window
lazydap break --list --format table

verified: false after a launch means the breakpoint is not attached to anything. Three causes, in order of likelihood:

1. The program is under /tmp on macOS. /tmp is a symlink to /private/tmp, lazydap sends the resolved path, the compiler recorded the unresolved one, and codelldb compares them as strings. Move the program under $HOME and rebuild. Full explanation: quirk 8.

2. The binary has no debug info. Rebuild with -g, and -O0 while you are there — an optimiser can move or delete the line you asked about.

Terminal window
gcc -g -O0 hello.c -o hello

3. The line has no code. A blank line, a comment, or a declaration the compiler emitted nothing for. Pick the next line that does something.

{"error":"SessionNotPaused","message":"... is running; pause it first (`lazydap pause --wait`) or wait for a breakpoint"}

stack, scopes, variables and eval need a stopped program. Either pause it:

Terminal window
lazydap pause --wait --format json

or set a breakpoint and continue --wait to it.

This most often follows a --wait that returned "state": "timeout". A timeout does not pause anything — the program is still running, and that is deliberate.

{"details":{"handle":2,"kind":"variables reference","stale":true},"error":"StaleHandle","message":"StaleHandle: variables reference 2 belongs to an earlier stop; the program has moved since. Ask `lazydap scopes` or `lazydap variables` again for this one"}

A variables_reference and a frame_id are lazydap’s own handles, minted per stop and never reused. They are valid only for the stop they came from, so any step or continue invalidates them — and one from a session that has ended says that instead.

The refusal happens before the debugger is asked anything, which is the point: the adapter’s own numbers get recycled, so a stale one could otherwise be answered with another stop’s variables under exit 0.

Call scopes again after every stop and use the new numbers.

One session per daemon. End the current one:

Terminal window
lazydap disconnect

Or run the second program under a different daemon:

Terminal window
LAZYDAP_INSTANCE=other lazydap launch ./other-program

A session whose program already finished is cleared automatically, so this usually means something is still paused somewhere.

A daemon from an earlier build is still running, typically right after a rebuild.

Terminal window
lazydap shutdown

The next command starts a current one. Nothing is lost except the live session; breakpoints are on disk.

codelldb is not on PATH. details.searched lists where lazydap looked.

Adapter discovery is PATH-only today — the config file that would let you point at an arbitrary binary is specified and not built. Follow the install guide, and check the wrapper script is executable:

Terminal window
codelldb --help

codelldb dies immediately, or dlopen fails

Section titled “codelldb dies immediately, or dlopen fails”
called Result::unwrap() on an Err value: "dlopen(.../liblldb.dylib, ...) (no such file)"

You installed codelldb as a symlink. It resolves liblldb relative to argv[0], so a symlink sends it one directory too high. Replace it with a wrapper script — quirk 1 has the exact three lines.

Every codelldb invocation hangs, including --help

Section titled “Every codelldb invocation hangs, including --help”

No output at all, forever, right after a macOS update. macOS has a stale per-inode security record for that file.

Re-copy the install so every file gets a new inode:

Terminal window
rm -rf ~/.local/opt/codelldb
cp -R <source>/extension ~/.local/opt/codelldb/extension
timeout 8 codelldb --help

Details: quirk 5.

eval says “use of undeclared identifier”

Section titled “eval says “use of undeclared identifier””

The variable is not in scope where the program is stopped. This is near-universal right after --stop-on-entry, which stops before main — the stack says _dyld_start and none of your program’s variables exist yet.

Set a breakpoint past the declaration and continue --wait before evaluating anything.

If instead you get a memory read error, you are on --context repl, which runs an LLDB command rather than evaluating an expression. The default is watch and does the right thing — quirk 7.

--stop-on-entry reports "reason": "exception"

Section titled “--stop-on-entry reports "reason": "exception"”

It is not an error. codelldb implements entry-stop with a SIGSTOP, which LLDB files as an exception. lazydap reports the normalised "reason": "entry" and keeps the adapter’s word in raw_reason. Branch on reason.

Nothing settled inside the window, and the program is still running. Either it is slow or it is stuck, and lazydap does not guess.

Terminal window
lazydap continue --wait --timeout 120 # give it longer
lazydap continue --wait --timeout 0 # wait forever; now it is your problem
lazydap pause --wait # stop it and look at where it got to

LAZYDAP_TIMEOUT changes the 30-second default for every command.

It reconnects on its own, and starts a daemon if there is none — so a lazydap shutdown or a crash resolves itself. If it stays stuck, quit with q and start it again, then check lazydap logs --level warn for what the daemon made of it.

Read the daemon’s log. It records what the daemon and the adapter said to each other, which is usually more specific than the error that reached your terminal:

Terminal window
lazydap logs --level warn
lazydap logs --limit 50

Then open an issue with that output, your platform, and lazydap version --format json.