Skip to content

Debug Go with Bugsaur

Adapter: dlv (Delve)

Install the adapter

go install github.com/go-delve/delve/cmd/dlv@latest
command -v dlv

Configure

bugsaur init detects the project by go.mod and writes one profile per directory containing package main: the module root itself, and direct subdirectories of cmd/.

A single binary in the root

go-hello/
├── go.mod
├── main.go          package main
└── .env
version = 1
default = "go-hello"

[profiles.go-hello]
adapter = "dlv"
program = "."

The working directory is the root and .env is read — the same as go run . from the root.

Several commands under cmd/

go-order/
├── go.mod
├── .env
├── configs/dev.yaml
├── migrations/
└── cmd/
    ├── go-order/main.go     package main
    ├── worker/main.go       package main
    └── shared/shared.go     package shared  ← not a command, no profile
version = 1
default = "go-order"

[profiles.go-order]
adapter = "dlv"
program = "cmd/go-order"

[profiles.worker]
adapter = "dlv"
program = "cmd/worker"

Both profiles share one working directory — the module root — so .env, configs/ and migrations/ are visible to each command and cwd is not needed. Switch with :DebugStart worker.

Bugsaur builds Go itself

Unlike the other languages, Go binaries are built by Bugsaur before the adapter starts, in a building phase, and the adapter receives a finished binary. You do not configure this — it is on by default.

Why. dlv debug builds inside the launch request, puts the result in __debug_bin<random> and deletes it afterwards. Go does not cache a linked executable — it reuses one only at the same path. The random name meant every run paid for a full link. Measured on a service with a 74 MB binary:

dlv debug Bugsaur's own build
first run 14 s 25 s
repeat run, code unchanged 14 s every time ~2 s

Where. <root>/.bugsaur/build/<program name> — a stable path, which is the whole point. The key is the program, not the profile, so two profiles for one binary reuse a single build. The directory ignores itself, so there is nothing to add to your .gitignore.

With what. By default, what the adapter catalog knows about dlv: go build -gcflags=all=-N -l -o {out} {program}. A profile can override it:

[profiles.worker]
adapter = "dlv"
program = "cmd/worker"

[profiles.worker.build]
command = "go"
args = ["build", "-tags=dev", "-gcflags=all=-N -l", "-o", "{out}", "./cmd/worker"]

command and args are separate rather than one string: -gcflags=all=-N -l contains a space inside a single argument, and parsing a string would need quoting rules exactly where a mistake quietly breaks debug information. Substitutions: {out} is the binary being built, {program} is what program names.

When it does not build: in attach mode; for adapters with no build in the catalog; and when the profile names mode or program to the adapter itself in launch_arguments — that means you are driving the launch by hand.

If the build fails, the session goes to failed with the compiler's text and the adapter is never started.

Debug a test

[profiles.orders-test]
adapter = "dlv"
program = "internal/orders"
# `go test` runs the binary from the package directory — without this line
# `testdata/` is not found, because cwd defaults to the root.
cwd = "internal/orders"

[profiles.orders-test.launch_arguments]
mode = "test"

Or use the test-under-cursor flow, which needs no profile:

bugsaur gui --project . --test-at internal/orders/order_test.go:31

See Guides → Debug Go tests.

Long builds: hand Delve a finished binary

If the build is always slow, take it out of the session entirely:

go build -gcflags="all=-N -l" -o bin/api ./cmd/api
[profiles.api]
adapter = "dlv"
program = "bin/api"

[profiles.api.launch_arguments]
mode = "exec"

launch then answers immediately. The cost is rebuilding by hand after each edit.

Two different mode keys

mode at the profile level is launch / attach. mode inside launch_arguments is Delve's own mode — debug, test, exec — and it overrides the generated debug.

Monorepos and go.work

The root is the nearest module. A shared .env above it is one line:

# services/api/.bugsaur/config.toml
[profiles.api]
adapter = "dlv"
program = "."
env_file = "../../.env"

The marker is go.mod; detection does not know about go.work.

Diagnostics

Symptom Likely cause Where to look
Session fails with compiler output the build failed fix the code; the text is in the Logs panel
launch hangs for minutes Delve is building inside the request use mode = "exec", above
Thousands of threads listed those are goroutines Threads
testdata/ not found in a test cwd defaults to the root set cwd to the package directory
Session never starts dlv not on PATH Adapter fails to start