bazel-contrib / bazel-contrib/rules_go

nogo: crash reported as missing .facts output

Open
#4,698 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
1.5k
Forks
760
Avg merge
1d 11h
Merged PRs (30d)
12

Description

### What version of rules_go are you using?

v0.63.0

### What version of gazelle are you using?

v0.53.0

### What version of Bazel are you using?

9.2.0

### Does this issue reproduce with the latest releases of all the above?

Yes. All three versions above are the current latest releases, and the MVCE below reproduces on them. `go/tools/builders/nogo.go` and `go/tools/builders/constants.go` are also unchanged on `master` at time of filing.

### What operating system and processor architecture are you using?

macOS, arm64.

### Any other potentially useful information about your toolchain?

Go SDK 1.27.0, `darwin-sandbox` execution strategy, bzlmod.

### What did you do?

When the nogo binary crashes before it can run — e.g. an unrecovered panic in a dependency's `init()` — rules_go silently treats the crash as "nogo found lint findings". The action reports success, and Bazel then fails with a misleading error pointing at an arbitrary innocent package.

MVCE, six files. `golang.org/x/tools` is the only dependency.

`.bazelversion`

```
9.2.0
```

`MODULE.bazel`

```python
bazel_dep(name = "rules_go", version = "0.63.0")
bazel_dep(name = "gazelle", version = "0.53.0")

go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk")
go_sdk.download(version = "1.27.0")
go_sdk.nogo(nogo = "@//:nogo")

go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(go_deps, "org_golang_x_tools")
```

`go.mod`

```
module example.com/repro

go 1.27.0

require golang.org/x/tools v0.30.0
```

`BUILD` — register an analyzer that panics at init:

```python
load("@rules_go//go:def.bzl", "nogo")

nogo(
name = "nogo",
visibility = ["//visibility:public"],
deps = ["//panicky"],
)
```

`panicky/panicky.go`

```go
package panicky

import "golang.org/x/tools/go/analysis"

func init() {
panic("boom")
}

var Analyzer = &analysis.Analyzer{
Name: "panicky",
Doc: "never runs; panics at init",
Run: func(pass *analysis.Pass) (interface{}, error) { return nil, nil },
}
```

`panicky/BUILD` is a plain `go_library` on `panicky.go` with `deps = ["@org_golang_x_tools//go/analysis"]`.

`hello/hello.go` — any trivial library; it is never the problem:

```go
package hello

func Hello() string { return "hello" }
```

`hello/BUILD` is a plain `go_library` on `hello.go`.

### What did you expect to see?

The panic surfaces as the action's error.

### What did you see instead?

```console
$ bazel build //hello:hello
ERROR: hello/BUILD:3:11: output 'hello/hello.facts' was not created
ERROR: hello/BUILD:3:11: Running nogo on //hello:hello failed: not all outputs were created or valid
```

The panic is written to `bazel-bin/hello/hello_nogo/nogo.log` and never shown:

```console
$ cat bazel-bin/hello/hello_nogo/nogo.log
panic: boom

goroutine 1 [running]:
example.com/panicky.init.0()
panicky/panicky.go:7 +0x2c
```

#### Cause

`nogoViolation` is `2` (`constants.go:21-25`), and an unrecovered Go panic also exits `2`. `runNogo` therefore treats the crash as diagnostics, diverts the output to `nogo.log`, and returns `nil`:

```go
// go/tools/builders/nogo.go:139-153
if exitErr.ExitCode() != nogoViolation {
return errors.New(string(prettyOut))
}
outLog, err := os.Create(filepath.Join(outDirPath, nogoLogBasename))
...
// Do not fail the action if nogo has findings so that facts are
// still available for downstream targets.
return nil
```

Confirming the exit code directly:

```console
$ ./bazel-out/.../nogo_actual_/nogo_actual; echo "EXIT=$?"
panic: boom
EXIT=2
```

#### Why this isn't #4374

[#4374 "Gracefully handle a panicking analyzer"](https://github.com/bazel-contrib/rules_go/pull/4374) describes this symptom and is present in 0.63.0, but its `recover()` only wraps `pass.Analyzer.Run` (`nogo_main.go:489-497`). That covers panics *during* analysis; an `init()` panic happens before `main()` in a separate subprocess and never reaches it.

#### Impact

The reported package is unrelated to the fault, and the failure is global — every nogo action crashes, but only the first-scheduled target is named. Nothing in the console output mentions `nogo.log`. Diagnosing it means reconstructing the action command from `--subcommands` and re-running it by hand.

We hit this bumping `honnef.co/go/tools` to v0.8.0, which retired SA5011 ([dominikh/go-tools@5161aaa181](https://github.com/dominikh/go-tools/commit/5161aaa181)); `nogo-analyzer`'s `FindAnalyzerByName` panics on names the current staticcheck no longer registers. That side is fixed in [nogo-analyzer#53](https://github.com/sluongng/nogo-analyzer/pull/53) (unreleased) and we filter `-SA5011` locally, so this report is only about the failure being unreadable.

#### Possible fix

`nogo_main.go:100-110` writes the facts file *before* returning `nogoViolation`, and `-x` is always passed (`nogo.go:115`). So a missing facts file on exit 2 unambiguously indicates a crash, and `runNogo` could check for it before writing `nogo.log`. Happy to send a PR.

Renumbering `nogoViolation` off `2` would fix the collision at the root, but it's a builder↔binary protocol change and wouldn't catch non-panic crashes that exit 2.

---

*This report was drafted with AI assistance for clarity and completeness. A human reproduced the failure and verified the findings before filing.*

Contributor guide

Open the contributing guide

Research direction

Start with runNogo in go/tools/builders/nogo.go and the exit-code definition in go/tools/builders/constants.go. Reproduce the six-file example with bazel build //hello:hello, then inspect go/tools/nogo_main.go where the facts file is written. Done when an init panic is reported as the action error instead of being treated as lint findings.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
build-system, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.