windbag
A pre-commit linter that catches comments narrating a change — a ticket number, what the code used to do, hedging about whether it works — instead of documenting why the current code is the way it is. Checks Python, JavaScript/TypeScript, Terraform/HCL, Rust, and SQL (including dbt and SQLMesh templates), plus the markup formats that carry comments: YAML, HTML, and Markdown.
What it detects
| Rule | Severity | Flags |
|---|---|---|
TICKET_ID |
error | A ticket ID in a comment (SCA-533). Exempts TODO(SCA-600)-style tracked tasks and security-advisory IDs (CVE-, GHSA-, ...). |
HISTORY_NARRATION |
error | "was missing", "used to be", "no longer", "silently swallows", and similar. |
HEDGE_LANGUAGE |
error | "should work", "hopefully", "not sure why", "i believe", and similar. |
CROSS_FILE_REF |
warn | A pointer to another file/line (handler.py:147). Documentation URLs are exempt. |
VERBOSE_COMMENT |
warn | A comment that's long relative to what it documents. Markup files are exempt. |
OBVIOUS_COMMENT |
warn | A comment that just restates the line below it (// increment the counter above counter += 1). |
error rules fail the check; warn rules are reported but don't block.
Markup files
YAML comments (#) come from the YAML grammar, so a # inside a quoted
scalar stays data rather than becoming a comment. HTML and Markdown are
checked through <!-- ... -->; in Markdown, anything inside a fenced code
block is sample markup, not a comment, and is skipped.
The content rules — TICKET_ID, HISTORY_NARRATION, HEDGE_LANGUAGE,
CROSS_FILE_REF — carry the weight here. VERBOSE_COMMENT does not apply:
a few lines of explanation above a one-line config key is the idiomatic
shape in a config file, not a comment outgrowing its code.
SQL files
.sql files are read as Jinja-templated SQL, which is what dbt and SQLMesh
models are. --, /* */, and Jinja {# ... #} comments are all checked. A
marker inside a 'string', a "quoted identifier", a $$ ... $$ body, or a
{{ ... }} / {% ... %} tag is data the template emits, not a comment. The
scanner is dialect-agnostic, so Snowflake, Postgres, BigQuery, and the rest
all work; MySQL-style # line comments are the one form it does not read.
A comment is measured against the statement below it: the non-blank lines
that follow, through the first one ending in ;.
Install
From PyPI — prebuilt wheels for macOS, Linux, and Windows, no Rust toolchain required:
pip install windbag
# or
uv tool install windbag
Run it without installing anything:
uvx windbag check --all
Building from source instead needs a Rust toolchain. If you don't have one:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
. "$HOME/.cargo/env" # and add this line to ~/.zshrc
Then, from a checkout:
cargo install --path .
That puts windbag in ~/.cargo/bin, which must be on your PATH. This
repo builds as a Python wheel via maturin's
bindings = "bin" mode, which just wraps the compiled binary — there's no
Python code here, and import windbag doesn't work:
uvx maturin build --release
uvx --from target/wheels/windbag-*.whl windbag check --all
Use
windbag init # write windbag.toml with generic defaults
windbag check --staged # check what's about to be committed
windbag check --all # check every git-tracked file in the repo
windbag check --json --staged # machine-readable output
windbag check --new-only f.py # only comments on lines the working tree adds over HEAD
Pre-commit:
- repo: local
hooks:
- id: windbag
name: windbag
entry: windbag check --staged
language: system
pass_filenames: false
types_or: [python, javascript, jsx, ts, tsx, terraform, rust, yaml, markdown, html, sql]
(windbag needs to already be on PATH — language: system doesn't install it for you.)
Claude Code plugin
Pre-commit catches slop after it's written. The plugin catches it as it's
written: a PostToolUse hook runs windbag on every file Claude edits and
exits non-zero on a violation, so the findings go straight back to Claude as a
blocking error and it rewrites the comment before moving on. A SessionStart
hook states the rules up front so most edits never trip the linter at all.
/plugin marketplace add scale-venture-partners/windbag
/plugin install windbag@windbag
The plugin ships the hooks, not the linter, so the binary also has to be on
PATH. The SessionStart hook installs it automatically on first use, via
whichever of uv tool install windbag, pipx install windbag, or
pip install --user windbag it finds first; if none of those are on PATH
either, it exits quietly and the PostToolUse hook stays a no-op until
windbag shows up some other way. To install it yourself instead — a
locked-down machine with no package-manager network access, say — see
Install.
To turn it on for everyone working in a given repo, commit this to that repo's
.claude/settings.json. Anyone who opens the repo is prompted to trust the
marketplace, and the hooks apply from their next session:
{
"extraKnownMarketplaces": {
"windbag": {
"source": { "source": "github", "repo": "scale-venture-partners/windbag" }
}
},
"enabledPlugins": { "windbag@windbag": true }
}
Working on the plugin itself? /plugin marketplace add /path/to/windbag points
at a local checkout instead. Either way the install copies plugin/
into ~/.claude/plugins/cache/ — keeping it out of the repo root is what keeps
target/ out of the copy. That copy is a snapshot: after editing a hook,
reinstall to pick up the change.
The hook needs windbag on PATH (or WINDBAG_BIN set) and jq installed;
without either it exits quietly rather than breaking the session.
| Env var | Effect |
|---|---|
WINDBAG_HOOK=off |
Disable both hooks without uninstalling. |
WINDBAG_HOOK_LEVEL=error |
Block only on error rules; ignore warnings. |
WINDBAG_BIN |
Explicit path to the binary. |
Only comments on lines the working tree adds over HEAD are reported, so
editing a file doesn't re-litigate comments that were already there. Claude is
told not to silence a rule with windbag: ignore on its own — a false positive
should surface to you, not get suppressed.
/windbag sweeps the whole repo and fixes what it finds.
Suppress a false positive inline:
# Was missing until v2 (LEGACY-1) — kept for the changelog. windbag: ignore[TICKET_ID]
Config lives in windbag.toml; see examples/scalevp.toml for narrowing TICKET_ID to a real tracker prefix instead of the generic default.
Examples
main.tf:218 error TICKET_ID comment references a ticket ID (SCA-533) — that
context belongs in the commit message or PR
description, not the code
main.tf:218 error HISTORY_NARRATION comment narrates the change ("was missing")
instead of the current state — describe the
constraint, not the history
main.tf:218 warn VERBOSE_COMMENT comment block is long relative to what it
documents (7 comment lines, 7.0x the 1 attached
code line(s))
# Was missing entirely (SCA-533): this used to silently no-op. <- TICKET_ID, HISTORY_NARRATION
value = fetch_value()
# This should work but I'm not sure why it fails sometimes. <- HEDGE_LANGUAGE
retry(fetch_value)
# increment the counter <- OBVIOUS_COMMENT
counter += 1
# TODO(SCA-600): revisit after Q3 pricing model ships <- clean, exempt
schedule_followup()
# Sorted DESC because the caller assumes the first row is newest. <- clean, real WHY
return sorted(items, reverse=True)
# Was missing (SCA-533): the deploy no longer fails. <- TICKET_ID, HISTORY_NARRATION
steps:
- checkout
# Pinned because the orb syntax below requires 2.1. <- clean, real WHY
version: 2.1
In Markdown, a comment shown as sample markup inside a fence is content:
<!-- Was missing (SCA-533): renders wrong without it. --> <- TICKET_ID, HISTORY_NARRATION
```html
<!-- Was missing (SCA-901): this one is an example. --> <- clean, inside a fence
```
License
MIT
Metadata
Release files for windbag 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| windbag-0.1.1.tar.gz | 38.7 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| windbag-0.1.1-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| windbag-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| windbag-0.1.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| windbag-0.1.1-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| windbag-0.1.1-py3-none-macosx_10_12_x86_64.whl | Python 3 | none | macOS 10.12+ x86-64 | Details |
Total release size: 8.6 MB
Release files / windbag-0.1.1.tar.gz
| Download URL | windbag-0.1.1.tar.gz |
|---|---|
| Size | 38.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b59e05ac7b1d5f9ddf43eed9547f441ee9f08ed36cd4fdca0da5fe1c2a1cad31
|
|
BLAKE2b-256 checksum How to use checksums |
d8382271ce3ec9099797b7ef7f76c2b88586691549ae7b3f786f2d09a91d6d70
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency logRelease files / windbag-0.1.1-py3-none-win_amd64.whl
| Download URL | windbag-0.1.1-py3-none-win_amd64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
c1bbb72de9dc1dd7bf48db9b1f8b8dce1af9570fa55a95a811abbe2f703e6af8
|
|
BLAKE2b-256 checksum How to use checksums |
dec2a3dad30f8b461a7cfe7f3ca9f789013683d796fccb5e10bce095df0e9d22
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency logRelease files / windbag-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | windbag-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 1.8 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
9c1d15a04a22cd7c32c2c19581a93f52fb85291ee1e2258f41384245d57234be
|
|
BLAKE2b-256 checksum How to use checksums |
019d168bbfbfd4958c10c618587495067f25be64f4bf2385d58ffea2b7e54da1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency logRelease files / windbag-0.1.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | windbag-0.1.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
f7a0960ee0741c0c4de64f0ea293e48797493e0c3dbf436f4cf271a69c13f311
|
|
BLAKE2b-256 checksum How to use checksums |
7228f1f1867b720a98130fdccb06ea7f3845aca12941fdbeccdc332266e3bc0d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency logRelease files / windbag-0.1.1-py3-none-macosx_11_0_arm64.whl
| Download URL | windbag-0.1.1-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
d007ea69ba6d402b11177c6621e49069075d2820d1ad0316e16590f96db47abc
|
|
BLAKE2b-256 checksum How to use checksums |
451443af46cd7bcd94ac0f9fa8f2e81ba53bea47055a1ac2a949734b6feb613e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency logRelease files / windbag-0.1.1-py3-none-macosx_10_12_x86_64.whl
| Download URL | windbag-0.1.1-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
96760ce6faf4ca6b0c7bb4938607ec8750c9ca03911c9b6eaca16681cee649f7
|
|
BLAKE2b-256 checksum How to use checksums |
199fb03d0719f23ca44356348f12108af7eaa0ba200a0c6d4feda89a1517a30d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency log