statix: an AST based linter for the Nix language
lints and suggestions for the nix programming language
At a glance
- What is it?
- statix is a Rust linter and fixer for Nix code that works purely on the parsed syntax tree, never evaluating your expressions. The original repository has also been superseded, since the project now lives on tangled and the Nixpkgs package is built from a separate fork.
- Who is it for?
- statix fits Nix developers who want antipattern findings on files before evaluation, in CI or in an editor, and it does not fit anyone who needs lints that depend on imports or attribute set contents, because the tool never evaluates Nix code.
- Can I use it commercially?
- Yes. MIT 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 105 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.
Star history
- oppiliappan/statix963 ★ · +2
| Repository | 2026-10-21 | 2026-11-07 |
|---|---|---|
| oppiliappan/statix | 961 | 963 |
2 Stars in the last 17 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Linting that stops at the syntax tree
statix exists to find antipatterns in Nix code, and the most important thing to know about it is what it refuses to do. The README states that statix works only with ASTs produced by the rnix-parser crate and does not evaluate any Nix code, with imports and attribute sets named explicitly as out of scope.
That boundary shapes everything else. A linter that evaluates your Nix can resolve an import and reason about what a set actually contains. statix cannot, so its findings are all syntactic: an assignment that should have been an inherit, a let binding that could collapse, redundant parentheses, a boolean expression that can be simplified. Nothing it reports depends on what your code means when it runs.
The upside is that checks are fast, deterministic and safe on files that do not even parse as a valid evaluation. The downside is equally concrete: a genuine logic mistake that depends on the value of an imported set will never be reported, and reading the lint list tells you exactly where the tool stops.
The implementation is Rust, the licence is MIT, and the repository is a Cargo workspace with members bin, lib, macros and vfs, a dev-flake directory, a flake.nix, and a vim-plugin directory at the root.
Two commands: check reports, fix rewrites
The tool has two entry points and the split between them is the design. statix check highlights antipatterns, statix fix can fix several such occurrences and apply the suggestion back to the source.
Findings carry a code and a message. The README example shows a check on tests/c.nix producing a finding labelled [W04] with the text Warning: Assignment instead of inherit from, and a pointer at the offending line. The same example shows statix fix --dry-run rendering a unified diff in which the assignment mtl = pkgs.haskellPackages.mtl; is rewritten as inherit (pkgs.haskellPackages) mtl;.
The dry run flag is what makes fix safe to try first, because it prints the diff and does not write to the file:
statix fix --dry-run /path/to/fileApplying the change for real is the same command without the flag:
statix fix /path/to/fileNot every lint has a suggestion, and the README is explicit that fix can fix several such occurrences rather than all of them. So fix is a partial rewriter, not a formatter that guarantees a clean run afterwards. The safe habit is to run check after fix and see what is left.
Installing from a flake, from nixpkgs or from brew
Three routes are documented. The flake route builds from source and then runs the binary from the result symlink:
# build from source
nix build git+https://git.peppe.rs/languages/statix
./result/bin/statix --helpThe same source also exposes a flake app, so you can run it without a build step:
# statix also provides a flake app
nix run git+https://git.peppe.rs/languages/statix -- --helpCachix is offered as a way to save time on builds:
# save time on builds using cachix
cachix use statixFrom Nixpkgs, the invocation is a nix run of the package:
nix run nixpkgs#statix -- helpAnd for macOS or Linux, Homebrew:
brew install statixThere is a catch with the Nixpkgs route that the README puts in a caution box, and it matters more than any of the commands above. The source for the Nixpkgs statix package is the Molybdenum Software fork, not this repository. So a developer who installs statix from nixpkgs and a developer who builds the flake from this repository can be running different code without noticing.
That caution also points out that the project is now hosted at tangled, that a significant fork by Molybdenum Software exists, and that the maintainer of that fork is eager to merge back.
Recursive check, ignore lists and the .gitignore switch
The check command takes a path and walks it, finding Nix files recursively and raising lints on them:
# recursively finds nix files and raises lints
statix check /path/to/dirGenerated files are the usual noise, and there is a short ignore flag for them. One file:
# ignore generated files, such as Cargo.nix
statix check /path/to/dir -i Cargo.nixSeveral files:
# ignore more than one file
statix check /path/to/dir -i a.nix b.nix c.nixOr a whole directory, which is the common case for .direnv:
# ignore an entire directory
statix check /path/to/dir -i .direnvstatix also respects your .gitignore if it exists, and there is a switch to turn that off:
# statix respects your .gitignore if it exists
# run statix in "unrestricted" mode, to disable that
statix check /path/to/dir -uThat -u flag matters in CI, where a generated tree that is not in .gitignore would otherwise be walked. The full option list is available from statix -h.
json and errfmt output for editors and CI
statix supports a variety of output formats, described in the README as standard, json and errfmt. The json format is conditional, and the documentation says so plainly:
statix check /path/to/dir -o json # only when compiled with --all-features
statix check /path/to/dir -o errfmt # singleline, easy to integrate with vimThe json availability condition is a real trap for anyone scripting against it. If you installed a binary that was not compiled with --all-features, the json output is not there to request, and the fix is to rebuild with that feature rather than to look for a flag on the command line.
The errfmt format exists for editor integration and the repository ships a vim-plugin directory, which is the intended pairing for it. A singleline format is what most editor status line and quickfix plugins expect, so errfmt is the format to feed an editor, while json is the format to feed a script that wants structured findings.
The dependency list in the workspace manifest shows the pieces behind that: ariadne for the rendered diagnostics, serde and serde_json for the json format, clap for the command line, ignore for the file walking that honours .gitignore, rayon for parallelism, and rnix and rowan for the parser and syntax tree.
statix.toml, the lint list and what can be disabled
Per project configuration lives in a statix.toml file at the project root. The file holds a list of disabled lint names:
# within statix.toml
disabled = [
"empty_pattern"
]Discovery is automatic. statix traverses the parents of the current directory looking for a statix.toml file, and the path can also be passed explicitly with the --config flag, which is available on both statix check and statix fix. The parent traversal means a monorepo can carry one configuration at the top and have it apply to every project underneath, and the explicit flag is the escape hatch for a file that lives somewhere else.
The lints themselves are listed in the README, with a pointer to statix list for an updated list:
bool_comparison
empty_let_in
manual_inherit
manual_inherit_from
legacy_let_syntax
collapsible_let_in
eta_reduction
useless_parens
empty_pattern
redundant_pattern_bind
unquoted_uri
empty_inherit
deprecated_to_path
bool_simplification
useless_has_attrAll lints are enabled by default, and statix dump generates a minimal config. The dump command is the practical way to start, because it generates a minimal statix.toml you can edit rather than trying to recall lint names from the documentation.
The names also tell you the scope. manual_inherit and manual_inherit_from cover the inherit rewrite shown in the README example, deprecated_to_path flags the older path syntax, and useless_has_attr and unquoted_uri are about expressions that cannot be simplified without evaluating anything.
Editorial conclusion
statix fits Nix developers who want antipattern findings on files before evaluation, in CI or in an editor, and it does not fit anyone who needs lints that depend on imports or attribute set contents, because the tool never evaluates Nix code. Verify first which statix you actually have, because the Nixpkgs package is built from the Molybdenum Software fork while this repository now points readers at a tangled hosted source, and check that your editor integration expects the json or errfmt output rather than the standard renderer.
Frequently asked questions
Does statix evaluate my Nix code?
No. The README states that statix works only with ASTs produced by the rnix-parser crate and does not evaluate any Nix code, with imports and attribute sets named as out of scope.
How do I install statix?
You can build it from a flake with nix build git+https://git.peppe.rs/languages/statix, run it with nix run nixpkgs#statix -- help, or install it with brew install statix. The README notes that the Nixpkgs package is built from the Molybdenum Software fork rather than this repository.
How do I disable a lint in statix?
Create a statix.toml file at your project root with a disabled list, for example disabled = [ "empty_pattern" ]. statix discovers the file by traversing the parents of the current directory, and you can point at a specific file with the --config flag on statix check and statix fix.
Why does statix not produce json output on my machine?
The README states that the json output format is only available when compiled with --all-features. The errfmt format is available without that condition and is the singleline format intended for vim integration.
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/oppiliappan-statix)Star history chart for your README
[](https://hysenlabs.com/projects/oppiliappan-statix)Was this useful?