Skip to content

Proposal: use ansible-static-lint in the pre-commit hook (526x faster, identical findings) #15038

Description

@arhuman

Summary

The ansible-lint pre-commit hook takes about 13 seconds on every commit that
touches a YAML file. I maintain ansible-static-lint
(astl), a Go reimplementation of ansible-lint's static rules that aims for
byte-identical output, and I benchmarked both tools on this repository
(HEAD 20e22a8). I would like to propose using it in the pre-commit hook,
while keeping ansible-lint in CI for full coverage.

Measurements

Same tree, same machine, hyperfine, 5 runs, warmup 1. ansible-lint 25.8.2
(the version uv run --with ansible-lint resolves under ansible==12.3.0),
full-tree invocation (240 files):

tool mean time
ansible-lint (no-arg form, 240 files) 13.128 s ± 0.043
astl 25.0 ms ± 0.3

That is a 526x speedup, and it turns the hook from a noticeable pause into
something below the perception threshold. The 13 s figure is a floor for the
real hook, since uv run adds its own environment resolution on top.

Findings parity

On the current tree, with .ansible-lint and .yamllint honoured
(exclude_paths, skip_list, enable_list, mock_modules), both tools report
zero findings on the full 240-file set. To verify that the double zero is
real agreement and not a discovery gap, I also ran both tools with the
.ansible-lint config removed: 270 findings match byte for byte
(name[missing], var-naming, name[casing], role-name, no-handler, key-order,
no-changed-when). The remaining differences are rules astl deliberately does
not port (fqcn, syntax-check, args, jinja, no-free-form) and a handful of
lines that differ between ansible-lint 25.8.2 and 26.8.0 themselves.

Scope caveat, stated plainly: astl covers ansible-lint's static rules only.
It does not run syntax-check or the fqcn/args/jinja families. That is why the
proposal is to swap the pre-commit hook, where latency matters on every
commit, and keep ansible-lint in CI as the authoritative gate.

A note on the current lint setup

While measuring, I noticed the current gates may not be checking what they
appear to:

  • The CI step runs uv run --with ansible-lint ansible-lint .. With an
    explicit . argument, ansible-lint only examines what its kind globs match
    at the top level: 6 files, of which 3 are the lint configs themselves. Your
    own green CI log shows it ("Passed: 0 failure(s), 0 warning(s) on 6
    files"). The no-argument form discovers 240 files. Dropping the . makes
    the CI gate actually lint the tree.
  • The pre-commit entry is
    bash -c 'uv run ansible-lint --force-color || echo "..."', so the || echo swallows the exit code and the hook can never fail a commit.

The tree is clean either way (I verified the 240-file run is green), so
nothing is currently slipping through, but both gates would report success
even if it were not.

Proposed change

- repo: https://github.com/arhuman/ansible-static-lint
  rev: v0.3.0
  hooks:
    - id: astl

pre-commit installs astl in an isolated environment and caches it; with
pre-commit 3.0 or newer, neither astl nor Go needs to be installed
beforehand. The first run bootstraps Go and compiles once, subsequent runs
use the cached binary. astl reads the repository's .ansible-lint and
.yamllint files, including exclude_paths, skip_list, enable_list, profile
and warn_list, so no configuration change is needed.

Happy to open a PR if there is interest.

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions