Files
da8eb79539 [AI] Fail agent hooks with an actionable error when jq is missing (#8651)
* [AI] Fail agent hooks with an actionable error when jq is missing

The shared agent-hook scripts all parse their payload with jq but never
checked it exists. A missing jq made git-guard.sh block every Bash call
with a misleading 'could not parse the hook payload' message, while other
hooks silently failed open and skipped their checks (issue #8629).

Add require_jq to common.sh — called by every hook before its first jq
use — so a missing jq fails closed with a clear install message, distinct
from a malformed-payload error. The script name in the message is derived
from $0 rather than passed by each caller. The Cursor adapters emit a
static JSON deny (their deny() helper itself needs jq to build JSON) or
an advisory where blocking isn't possible, and AGENTS.md now documents
the prerequisite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Address review: advisory wording for PostToolUse hooks, hook tests

PostToolUse hooks (format-edited-file, prefer-one-component) run after the
edit already happened, so a missing jq now reports 'Agent hook skipped'
instead of claiming a block. Exit 2 stays: it is the only exit code that
feeds stderr back to the agent. Add scripts/agent-hooks/test-hooks.sh, a
standalone runnable check covering the missing-jq path for every hook plus
malformed-JSON, missing-field, and guard-behavior cases, and soften the
release-note verb.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Remove standalone hook test script

Not wired into CI, so it only served as a manual check; the hook behavior
was verified by hand and the script isn't worth maintaining.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 06:52:52 +00:00

33 lines
1.5 KiB
Bash

#!/bin/sh
# Shared helpers for the agent-hook scripts. Source this file; don't execute it.
# Every hook parses its stdin payload with jq — a hard prerequisite. Call this
# before the first jq use so a missing jq fails with an actionable message,
# distinct from a malformed-payload error. PreToolUse hooks get a "Blocked:"
# message (exit 2 blocks the call); PostToolUse hooks pass "advisory" — the
# tool call already ran, so the message reports the skipped hook instead of
# claiming a block. Exit 2 stays in both modes: it is the only exit code that
# feeds stderr back to the agent rather than dropping it.
require_jq() {
command -v jq >/dev/null 2>&1 && return 0
if [ "${1:-}" = advisory ]; then
echo "Agent hook skipped: 'jq' is required but was not found on PATH. Install jq (https://jqlang.org). (scripts/agent-hooks/${0##*/})" >&2
else
echo "Blocked: the agent hooks require 'jq', which was not found on PATH. Install jq (https://jqlang.org) and retry. (scripts/agent-hooks/${0##*/})" >&2
fi
exit 2
}
# Resolve the repo root regardless of which agent invoked the hook.
# Optional $1: a directory hint for the `git` fallback (e.g. an edited file's dir).
resolve_repo_root() {
if [ -n "${CLAUDE_PROJECT_DIR:-}" ]; then
printf '%s\n' "$CLAUDE_PROJECT_DIR"
return 0
fi
_hint=${1:-.}
_root=$(git -C "$_hint" rev-parse --show-toplevel 2>/dev/null) || _root=
[ -n "$_root" ] || _root=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
printf '%s\n' "$_root"
}