NilAway will analyze your dependencies unless you stop it, and it caches facts in memory in its simplest mode
Static analysis tool to detect potential nil panics in Go code. NilAway [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [!WARNING] NilAway is currently under active development: false positives and breaking changes can happen.
At a glance
- What is it?
- uber-go/nilaway is a go/analysis-based static checker that reports potential nil panics together with the nilness flows behind them, tracking them across package boundaries rather than inside one package. By default it reads all Go code including the standard library, which the project says costs performance and produces errors you cannot act on, and its standalone checker holds every fact in memory.
- Who is it for?
- NilAway is worth the setup for a Go service where a nil panic is a page at three in the morning, because it does two things the standard nilness analyzer does not: it follows nilness across packages, and it hands you the flow that produced the report instead of a bare line number.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.
Star history
- uber-go/nilaway3,933 ★ · +54
| Repository | 2026-08-04 | 2026-11-13 |
|---|---|---|
| uber-go/nilaway | 3.9k | 3.9k |
54 Stars in the last 101 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
By default it reads every dependency, which is a deliberate cost
The first thing to change before you run this is the scope. By default NilAway analyses all Go code, and the readme is explicit that this includes the standard libraries and your dependencies. The reason given is a good one: understanding dependency code is what lets it reduce false negatives, because a function that returns a nil from another package has to be seen to be understood. The cost is stated just as plainly. There is a significant performance cost, paid once for drivers that support modular analysis, and there is an increase in non-actionable errors inside dependencies, which matters most in a large project with many dependencies. The instruction that follows is to use the include-pkgs flag to narrow the analysis to your own code. It comes with the motivation stated as well: focus solely on the potential nil panics reported in first-party code. The standalone invocation is:
nilaway -include-pkgs="<YOUR_PKG_PREFIX>,<YOUR_PKG_PREFIX_2>" ./...Facts live in memory in the standalone checker, which caps its usefulness
The reason the project steers you away from its own standalone binary is a caching detail, and it is the kind that decides whether a tool scales or does not. NilAway uses the fact mechanism from the go/analysis framework to cache what it learns about a package. A driver with modular support persists those facts between runs, so work already done is not repeated. The standalone checker stores all facts in memory, so every run starts from nothing. The readme calls it highly recommended to use a driver that supports modular analysis, naming bazel/nogo and golangci-lint and excluding the standalone checker explicitly, and then says the standalone checker exists more for evaluation purposes since it is easy to get started. Installation is one command:
go install go.uber.org/nilaway/cmd/nilaway@latestNote what @latest means for a tool that publishes no GitHub releases: there is no tagged version to pin against.
It is not a golangci-lint linter yet, only a plugin
The integration story starts with an apology. NilAway in its current form can report false positives, which the readme says hinders its immediate merging into golangci-lint and being offered as a linter, with a pull request reference given. So there is no line to add to an enabled linters list and no install step. You build it as a plugin and run a private binary instead. There are two plugin systems in golangci-lint and the module plugin system, introduced in v1.57.0, is described as both much easier to use and the only supported approach. That version floor is repeated in the configuration file it asks you to create at the repository root:
# This has to be >= v1.57.0 for module plugin system support.
version: v1.57.0
plugins:
- module: "go.uber.org/nilaway"
import: "go.uber.org/nilaway/cmd/gclplugin"
version: latest # Or a fixed version for reproducible builds.A binary you build yourself, cached by config hash or by date
Once the module plugin is declared, the linter has to be rebuilt rather than reinstalled, and the readme treats the cost of that as worth managing. The bootstrap golangci-lint must itself be v1.57.0 or newer, and running custom produces a binary in the current directory named custom-gcl, whose name and location can be changed in the same configuration file. After that you run the custom binary instead, with the same arguments you would pass to the original:
./custom-gcl run ./...The caching tip is the part with a real trap in it. If you pin a fixed NilAway version, the hash of the configuration file is a sound cache key, because the file is what determines the build. If you leave the version as latest, that hash stops changing when the tool changes underneath you, so the advice is to append the build date to the key to force expiration after a chosen period. That is the project telling you what untracked latest versions cost.
JSON output needs pretty-print switched off
One flag pair matters if you plan to consume the results rather than read them. The tip is to disable the pretty-print flag when outputting JSON:
nilaway -json -pretty-print=false -include-pkgs="<YOUR_PKG_PREFIX>,<YOUR_PKG_PREFIX_2>" ./...That is the whole of it, and it is the kind of detail that only exists because someone fed formatted output into a parser. Worth pairing with the other structural fact about the tool's output: reports come with the nilness flows that produced them, which is the stated difference from the standard nilness analyzer in the standard library. That analyzer works within a package; NilAway tracks nil flows within a package and across packages, and gives you the path rather than the verdict. Anything that reads NilAway's JSON therefore gets the flow data too, so a consumer that only wants file and line has to discard it.
The overhead claim is a measurement, and the guarantee is a partial one
Three properties are claimed, and each one is qualified in a way that tells you what you are buying. The first is automation: an inference engine means no annotations and no extra information from developers beyond ordinary Go code, which is the difference from a checker that needs you to write down contracts. The second is speed, and this is the one with a number: in the project's own measurements, less than five percent build-time overhead when NilAway is enabled, with further optimisations described as ongoing. The third is practicality, and it is stated as a limit rather than a boast: NilAway does not prevent all possible nil panics, it catches most of the ones observed in production, which is how it keeps a balance between usefulness and build-time overhead. So the contract is a large reduction, not elimination, and the evidence offered is the authors' own measurements.
The warning at the top of the file is the release policy
The first thing the readme shows is a warning block, and it is the most important sentence in the file: NilAway is currently under active development, false positives and breaking changes can happen, and feedback is welcome. That single line governs everything else about adopting it. The repository publishes no GitHub releases at all, so there is no changelog with version headings and no tag to pin a build to, which means the documented install of the binary at latest tracks the main branch, and the module plugin configuration offers latest as the default with a fixed version mentioned only as the reproducible alternative. The last push to the default branch is 2026-09-18. Whether that is recent enough for your team is a judgement about your tolerance for a linter that can change its output between runs, and it is worth making that call deliberately rather than by default.
The module is go.uber.org and the tree is a set of named analysis stages
The repository is laid out as the pipeline it implements rather than by feature. At the top level sit annotation handling, assertion checking, the inference engine, an accumulation stage, config, diagnostic rendering, guard logic, hooks, utility code, the cmd directory holding the two entry points, and testdata with a test harness directory next to it. The module path is go.uber.org/nilaway and the go directive asks for 1.26.0, which tells you the toolchain floor before you read a line of the readme. Dependencies are few and pointed: the golangci-lint module plugin register, go-cmp, a compression library, testify, goleak for leak detection in tests, and golang.org/x/tools at v0.50.0. The Makefile sets GOBIN into the project's own bin directory so tools never touch your global path, keeps a list of independent modules covering the root and a tools directory, and runs tests under the race detector.
Editorial conclusion
NilAway is worth the setup for a Go service where a nil panic is a page at three in the morning, because it does two things the standard nilness analyzer does not: it follows nilness across packages, and it hands you the flow that produced the report instead of a bare line number. Do not add it to a codebase with no include-pkgs setting, since the default behaviour is to analyse your dependencies too, and every error from a third-party module is noise you will pay for on every run. Two practical notes. The project carries a warning that it is under active development, with false positives and breaking changes expected, and it publishes no GitHub releases, so installing at latest means installing whatever is on main. And do not use the standalone checker as your production driver, because it keeps all cached facts in memory and the ones meant for real work, bazel/nogo and golangci-lint, keep them on disk.
Frequently asked questions
What does nil mean in Golang, and what does NilAway do about it?
That question is about the language, and this repository does not define Go's zero values. What NilAway states is the consequence: nil panics otherwise reach production and are caught at runtime, and the tool exists to catch them at compile time instead, reporting the nilness flow behind each report rather than a bare line number.
How do I run NilAway on my own code only?
Set the include-pkgs flag to your package prefixes, for example nilaway -include-pkgs="<YOUR_PKG_PREFIX>,<YOUR_PKG_PREFIX_2> ./... . By default the analyzer reads all Go code including the standard library and your dependencies, which the project says costs performance and produces non-actionable errors. Under golangci-lint the equivalent setting is an include-pkgs key inside the nilaway custom linter settings.
Can I install NilAway as a normal golangci-lint linter?
Not yet, and the project says why: NilAway in its current form can report false positives, which hinders its merging into golangci-lint as a linter. You have to build it as a plugin using the module plugin system introduced in golangci-lint v1.57.0, run golangci-lint custom to produce a custom-gcl binary, and then run that binary instead of the original.
Is NilAway still under active development?
It says so. A warning block at the top of its readme states that NilAway is under active development, that false positives and breaking changes can happen, and that feedback is welcome. The repository publishes no GitHub releases, and the last push to the default branch is 2026-09-18.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/uber-go-nilaway)Star history chart for your README
[](https://hysenlabs.com/projects/uber-go-nilaway)Was this useful?