test-hook.sh reports valid JSON as invalid when jq is not installed

Status Open
Reported on v2.1.219
Maintainer reply ✓ Yes — bcherny
Activity 4 comments · opened Aug 4, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

When jq is not installed, plugin-dev/skills/hook-development/scripts/test-hook.sh reports that the test input is invalid JSON. The input is valid. The script fails closed with a wrong diagnosis, which sends the user to debug their input instead of installing the missing dependency.

Root cause

Line 155:

if ! jq empty "$TEST_INPUT" 2>/dev/null; then
  echo "❌ Error: Test input is not valid JSON"

2>/dev/null discards the shell's jq: command not found, so a missing binary is indistinguishable from a parse failure.

Reproduction

On a machine without jq (common on Windows):

$ node -e "JSON.parse(require('fs').readFileSync('input.json','utf8')); console.log('valid')"
valid
$ bash test-hook.sh guard.mjs input.json
❌ Error: Test input is not valid JSON
$ echo $?
1

Expected

A missing dependency should be reported as a missing dependency.

Suggested fix

Guard on availability before use:

if ! command -v jq >/dev/null 2>&1; then
  echo "Error: jq is required but not installed. See https://jqlang.github.io/jq/"
  exit 1
fi

Worth noting that references/hooks.md in the Claude Code docs already warns against depending on jq inside hook handlers for this exact reason ("jq is absent on many Windows installs"). The same caution applies to the tooling that tests those handlers.

Environment: Claude Code 2.1.219, Windows 11, Git Bash, Node 24.14.1, no jq on PATH.

View original on GitHub ↗

4 Comments

TranDenyDFW · 26 days ago

Correction to my own report, please disregard the final paragraph.

I wrote that "references/hooks.md in the Claude Code docs already warns against depending on jq inside hook handlers". That attribution is wrong and I withdraw it.

  • There is no references/hooks.md page in the Claude Code documentation. The sentence I quoted is from a reference file in my own project, and my own evidence ledger records it as unpublished own-work research, not as a documented statement from this project.
  • The actual hooks documentation says the opposite of what I implied: "This script and the Bash examples on this page that parse JSON input use jq, so install jq and make sure it is on your PATH before trying them."

So jq is a deliberate and documented dependency, and I should not have presented my own note as your guidance. Apologies for the noise.

The reported defect itself is unaffected, and I have re-checked it. With jq absent, test-hook.sh line 155 reports:

❌ Error: Test input is not valid JSON

for input that JSON.parse accepts, because jq empty "$TEST_INPUT" 2>/dev/null discards the shell's command not found. The suggested fix stands: check for the dependency explicitly so a missing binary is reported as a missing binary rather than as malformed input.

PranshulSoni · 6 days ago

Thanks for the correction and for re-checking the behavior.

That clarification makes sense. jq is an intentional and documented dependency for these hook examples. The proposed change does not remove or replace that dependency; it only makes the failure mode accurate when the executable is missing.

I independently reproduced the remaining defect. When jq is absent, valid JSON is reported as invalid because the command jq empty "$TEST_INPUT" 2>/dev/null conflates a missing command with a JSON parsing failure.

The fix adds an explicit availability check so users receive:

Error: jq is required but was not found on PATH

Malformed JSON still receives the existing invalid-JSON diagnostic.

The verified implementation and regression test are available here:

https://github.com/PranshulSoni/claude-code/tree/fix/plugin-dev-missing-jq-diagnostic

The commit is:

7c46dd4ecaf397c690bff21d800b65a3ea09bde4

My account can push to the fork, but GitHub does not allow it to create pull requests against anthropics/claude-code. A maintainer can open a pull request from the branch or cherry-pick the commit.

bcherny collaborator · 5 days ago

Reproduced. With a syntactically valid JSON test input and jq absent from PATH, test-hook.sh reports "❌ Error: Test input is not valid JSON" and exits 1; the identical invocation with jq installed proceeds normally. The script redirects stderr to /dev/null around the jq validation call, so "command not found" is indistinguishable from a real parse failure and gets misreported as invalid input. The suggested up-front dependency check (fail with a clear "jq is required but not installed" message) is the right shape of fix, and the same missing-jq misdiagnosis would also affect the script's later jq calls. Thanks for the precise report and root cause.

🤖 Generated with Claude Code

Malwurf · 3 days ago

Branch with the fix, extended to cover the second script:

Diff: https://github.com/anthropics/claude-code/compare/main...Malwurf:claude-code:fix/jq-dependency-check
Commit: https://github.com/Malwurf/claude-code/commit/388092dec0bec62ad9acffea02645d828b30b3fc

Credit to @PranshulSoni, who root-caused this and published a fix first; they are the co-author on the commit. Two things added on top:

  1. validate-hook-schema.sh:32 has the identical bug and is not mentioned in the issue — if ! jq empty "$HOOKS_FILE" 2>/dev/null prints ❌ Invalid JSON syntax on a valid file when jq is absent. It is fixed here too.
  2. The guard sits after argument parsing rather than next to the JSON check, so --help, --create-sample and the usage output still work without jq, while every later jq call is covered — per @bcherny's note that the later calls are affected as well.
if ! command -v jq >/dev/null 2>&1; then
  echo "❌ Error: jq is required but not installed: https://jqlang.github.io/jq/download/" >&2
  exit 1
fi

Repo-wide sweep for the misdiagnosing pattern (jq with stderr silenced): these two files are the only occurrences. hook-linter.sh never invokes jq — its line 62 is grep -q "jq" "$script", which inspects other people's scripts. The remaining jq callers (.devcontainer/init-firewall.sh, ralph-wiggum/hooks/stop-hook.sh, scripts/*.sh) do not silence stderr, so a missing jq already surfaces honestly there. The examples/*.sh snippets are left alone deliberately — they are meant to be copied into a user's own project.

Verification

jq lives in /usr/bin here, so the missing-jq case was reproduced with a PATH of symlinks to every binary in /usr/bin and /bin except jq. Input confirmed valid with python3 -m json.tool.

Before:

$ PATH="$NOJQ" bash test-hook.sh examples/validate-bash.sh valid.json
❌ Error: Test input is not valid JSON                      # exit 1

$ PATH="$NOJQ" bash validate-hook-schema.sh hooks.json
Checking JSON syntax...
❌ Invalid JSON syntax                                      # exit 1

After:

$ PATH="$NOJQ" bash test-hook.sh examples/validate-bash.sh valid.json
❌ Error: jq is required but not installed: https://jqlang.github.io/jq/download/

$ PATH="$NOJQ" bash validate-hook-schema.sh hooks.json
❌ Error: jq is required but not installed: https://jqlang.github.io/jq/download/

With jq present, output is byte-identical before and after for both scripts (diffed; the only delta is test-hook.sh's Duration: Ns wall-clock line). --help, --create-sample PreToolUse and the no-args usage all still work without jq. bash -n passes; shellcheck 0.11.0 reports 0 findings on test-hook.sh and the same 12 pre-existing findings on validate-hook-schema.sh before and after.

@PranshulSoni also wrote a 36-line regression test. I left it out because the repo carries no test files anywhere and no workflow that would run one — happy to add it if you'd like it.

I could not open a pull request either: CreatePullRequest returns Malwurf does not have the correct permissions. Cross-fork PRs from author_association=NONE accounts appear to have stopped on this repo after 2026-08-16, which matches what @PranshulSoni and #90065 report. Cherry-pick or pull from the branch above as you prefer.