A Claude Code
PostToolUsehook that blocks unnecessary code comments with checkable, cited reasons — while sparing earned API documentation, directives, and non-obvious intent.
pnpm install -g @systemfsoftware/claude-code-comment-checker$ echo '{"tool_name":"Write","tool_input":{"file_path":"src/load_config.py","content":"import json\n\ndef load_config(path):\n # Parse the config file\n data = json.load(open(path))\n # TODO: fix this later\n # print(data)\n return data\n"}}' | comment-checker
An automated reviewer flagged 3 comment(s) in src/load_config.py as unnecessary.
Each is stated with the specific reason it should be removed. Do not
dismiss these as "justified" — the reason is given so the claim can be
checked, not argued away.
line 4 — # Parse the config file — restates what the code already says (shares config, file, parse)
line 6 — # TODO: fix this later — a TODO with no tracked reference — file a ticket or delete it
line 7 — # print(data) — dead code left in a comment
Action: delete the flagged comments. If the code is unclear without
one, make the code self-explanatory instead — better names, extraction,
a clearer type — and do not re-add the comment.
Most comment linters rely on blunt allowlists: they flag every comment that lacks a specific annotation, or blindly permit any text placed inside a docstring block. This creates high false-positive noise that trains agents and engineers to dismiss warnings entirely.
comment-checker uses tree-sitter AST extraction across 37 programming languages and evaluates comments against prioritized classification rules. When a comment is flagged, the hook provides concrete citations — such as token overlap percentages or verb-to-operator mappings — allowing the agent to verify why a comment failed and fix the underlying code rather than arguing with the tool.
| Capability | Flag-everything linters | comment-checker |
|---|---|---|
| Classification model | Blunt allowlist or regex scan | Prioritized rule tables with syntactic AST context |
| Public API docstrings | Flagged or blindly permitted | Spared when containing structured contract tags (@param, Args:, Returns:) |
| Flag feedback | Generic warning message | Specific, checkable reason citing token overlap and operator evidence |
| Incremental edits | Re-evaluates entire source file | Evaluates only newly added comments; skips fragment restatements |
| Evaluation standard | Ad-hoc heuristics | F1 ≥ 0.85 on 60-case multi-language benchmark (eval/corpus.json) |
Recommended (npm / pnpm / yarn / bun):
pnpm install -g @systemfsoftware/claude-code-comment-checkerThe package distributes prebuilt native binaries for Linux (x64, arm64), macOS (x64, arm64), and Windows (x64) via optionalDependencies. Package managers install only the single binary target required for your operating system. No postinstall lifecycle scripts run during installation, ensuring full compatibility with --ignore-scripts.
Other install methods (Cargo, direct binary download)
Install via Cargo (requires Rust 1.85+):
cargo install --git https://github.com/systemfsoftware/comment-checker --package claude-code-comment-checkerDirect download:
Prebuilt tarballs (comment-checker-<target>.tar.gz) for all supported platforms are attached to every GitHub Release.
Add comment-checker as a PostToolUse hook in your user configuration (~/.claude/settings.json) or project configuration (.claude/settings.json):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{ "type": "command", "command": "comment-checker" }
]
}
]
}
}When an agent writes code containing justified comments or clean documentation, the tool exits cleanly with status code 0:
$ echo '{"tool_name":"Write","tool_input":{"file_path":"src/client.py","content":"# SPDX-License-Identifier: Apache-2.0\ndef load(path):\n return open(path).read()\n"}}' | comment-checker
[check-comments] Skipping: No unnecessary comments foundcomment-checker identifies unnecessary comments across five distinct categories:
| Category | Reason cited | Example |
|---|---|---|
| Restates the code | restates what the code already says (<token overlap / operator mapping>) |
// adds one to one adjacent to x += 1 |
| Narrates control flow | narrates the <construct> construct the code already shows |
// loop over each item adjacent to for item in items: |
| Change-log memo | describes what changed, not why — git history already records this |
// Changed from old_value to new_value |
| Dead code | dead code left in a comment |
// fmt.Println("debug") |
| Untracked TODO | a TODO with no tracked reference — file a ticket or delete it |
// TODO: fix this later |
Comments that provide non-redundant intent or satisfy interface documentation standards are classified as justified and pass without warnings:
- License & generated headers — SPDX identifiers, copyright lines, and generated-file notices (
// SPDX-License-Identifier: Apache-2.0,/* Copyright (c) 2026 ... */) - Compiler & linter directives —
# noqa: E501,// @ts-ignore,// eslint-disable-next-line,# shellcheck disable=SC2086,// clippy::too_many_arguments,/* istanbul ignore next */ - BDD test steps —
# given,# when,// then - Structured API docstrings & contract tags — Docstrings containing
@param,@returns,Args:,Returns:,# panics, or# safety, as well as leading contract tags on declarations - Non-obvious intent & rationale — Comments explaining why something is done (
// workaround: SDK panics on empty input,# because SQLite locks during write,// to avoid TOCTOU race,Why: 1-based index) - Attribution & references —
// @author Jane Doe,// ref: https://...,// adapted from ... - Executable shebangs —
#!/usr/bin/env python3,#!/bin/bash
Tree-sitter AST parsers are compiled directly into the binary across 37 programming languages and formats:
| Category | Languages |
|---|---|
| Systems & Native | Rust (.rs), C (.c, .h), C++ (.cpp, .cc, .cxx, .hpp), Zig (.zig) |
| Web & Applications | TypeScript (.ts, .tsx), JavaScript (.js, .jsx, .mjs, .cjs), Python (.py, .pyi), Go (.go), Java (.java), C# (.cs), Kotlin (.kt), Scala (.scala), Swift (.swift), Dart (.dart), PHP (.php), Ruby (.rb), Elixir (.ex, .exs), Svelte (.svelte), Elm (.elm), Lua (.lua), Groovy (.groovy, .gradle), OCaml (.ml, .mli), Haskell (.hs), R (.r, .rmd) |
| Shell & Config | Bash / Shell (.sh, .bash, .zsh), SQL (.sql), JSON (.json), YAML (.yaml, .yml), TOML (.toml), HTML (.html), CSS (.css), Dockerfile (Dockerfile), HCL / Terraform (.tf, .hcl), CUE (.cue), Protocol Buffers (.proto), Markdown (.md) |
Files written in unsupported extensions or non-code formats are skipped automatically, allowing standard tool execution to proceed without interruptions.
The --prompt command-line option allows teams to override the default notification text delivered to Claude Code. The {{comments}} placeholder is replaced with the structured list of flagged comments and citations:
comment-checker --prompt "Review feedback:\n\n{{comments}}\n\nPlease revise the code."You can configure this flag directly in your .claude/settings.json file:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "comment-checker --prompt \"Violations detected:\n\n{{comments}}\""
}
]
}
]
}
}comment-checker returns deterministic status codes suitable for shell scripts and automated editor integrations:
| Exit Code | Status | Description |
|---|---|---|
0 |
Pass | No unnecessary comments found, clean payload, or unparseable input (hook never blocks on invalid input) |
2 |
Block | One or more unnecessary comments detected; diagnostic report emitted to stdout |
Q: command not found: comment-checker after installation.
A: Verify that your global package bin directory is included in your shell PATH environment variable. For global npm or pnpm installations, you can check active bin paths with npm bin -g or pnpm root -g.
Q: Why are comments in Edit or MultiEdit tool calls treated differently than Write?
A: When Claude Code uses Edit or MultiEdit, only the newly added comments in the diff are evaluated; pre-existing comments in the file are ignored. Furthermore, since code fragments may lack surrounding AST context, restatement detection is disabled on fragments to prevent false positives.
Q: Does comment-checker modify my source files?
A: No. comment-checker is purely diagnostic. It emits a report to stdout and exits with code 2 to inform the agent of the required correction.
Q: Does comment-checker transmit code over the network?
A: No. The binary runs entirely locally, processes JSON over stdin, and makes no network requests.
Releases are triggered by semantic tags pushed to main (such as v0.1.0), which automatically executes .github/workflows/release.yml. The pipeline compiles release binaries across all matrix targets, validates cryptographic checksums, and publishes each platform package followed by the root launcher with npm OIDC provenance.
Detailed release specifications, matrix bindings, and trusted publisher instructions are documented in docs/plans/2026-08-17-001-feat-npm-distribution-release-plan.md.
Development setup, test execution, and mutation testing guidelines are maintained in AGENTS.md.
Distributed under the Apache-2.0 License. © System F Software