Hooklint banner
ethan-haas ethan-haas

Hooklint

Development community

Description

Offline linter for the agent config that actually executes: hooks, skills, MCP entries, permission rules. Catches the hook that can never fire and the hook that splices agent-controlled text into a shell command unquoted.

Installation

This entry records only its repository, not the path inside it, so there is no exact command to give. Open the source below and copy the folder into ~/.claude/skills/, or the file into ~/.claude/agents/.

README

hooklint

You pasted a hook out of a 53,000-star awesome-list into a tool that executes shell on your machine. Two failure modes look identical in the config file: a hook whose matcher can never fire, and a hook that splices tool-output or fetched content into a shell command unquoted. `hooklint` finds both, and six more, anchored to a JSON Pointer into the offending file. It parses; it never executes a hook.

$ hooklint .claude/settings.json
ERROR  .claude/settings.json  /hooks/PreToolUse/0/matcher  [dead_matcher]
       PreToolUse hook #0: matcher 'Bahs' matches none of the declared tool names; this hook will never fire
       evidence: 'Bahs'

hooklint: scanned 1 file(s), 1 finding(s), 0 parse error(s), unknown_rate=0.0000 (0/9)

That's a typo (`Bahs` for `Bash`) that JSON-validates fine and silently does nothing, forever. Here's the other failure mode, from a generic YAML hook manifest:

$ hooklint agent-hooks.yaml
ERROR  agent-hooks.yaml  /hooks/pre_tool_use/0/run  [unquoted_interpolation]
       unquoted/double-quoted shell expansion '$USER_MESSAGE'; if the expanded value is tool output or fetched content this is command injection
       evidence: $USER_MESSAGE

hooklint: scanned 1 file(s), 1 finding(s), 0 parse error(s), unknown_rate=0.0000 (0/6)

That one runs. It just runs whatever `$USER_MESSAGE` happens to contain, unquoted, in a shell.

Why this one

what exists stars
hesreallyhim/awesome-claude-code 53.4k
PatrickJS/awesome-cursorrules 40.7k
wshobson/agents 39.4k
any linter for these configs 63

The ecosystem produced collections, not verification.

Install

pipx run hooklint /path/to/repo

No account, no API key, no network, no client installed. Or:

pip install hooklint
hooklint .                 # scan the current directory
hooklint --json .          # machine-readable
python -m hooklint .       # also works

What it checks

Hook commands are parsed as **s