ethan-haas

Hooklint — Development skill for Claude Code

Development community

Offline linter for the agent config that actually executes: hooks, skills, MCP entries, permission rules.

How to install Hooklint

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

What Hooklint does

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.

Alternatives in Development

  • Domxss — Confirm DOM XSS in a real headless browser — injects canary payloads into params + URL fragment and only repor 4.5k ★
  • Sandbox-agent (Rivet) — Run Claude Code and other coding agents in sandboxes controlled over HTTP 1.2k ★
  • Fable OS — An agentic operating system where the kernel is controlled directly by Claude 324 ★

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