Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Guidance for developing lifecycle hooks for Claude Code plugins from the official Anthropic repository.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/hook-linter.sh
1#!/bin/bash2# Hook Linter3# Checks hook scripts for common issues and best practices45set -euo pipefail67# Usage8if [ $# -eq 0 ]; then9echo "Usage: $0 <hook-script.sh> [hook-script2.sh ...]"10echo ""11echo "Checks hook scripts for:"12echo " - Shebang presence"13echo " - set -euo pipefail usage"14echo " - Input reading from stdin"15echo " - Proper error handling"16echo " - Variable quoting"17echo " - Exit code usage"18echo " - Hardcoded paths"19echo " - Timeout considerations"20exit 121fi2223check_script() {24local script="$1"25local warnings=026local errors=02728echo "๐ Linting: $script"29echo ""3031if [ ! -f "$script" ]; then32echo "โ Error: File not found"33return 134fi3536# Check 1: Executable37if [ ! -x "$script" ]; then38echo "โ ๏ธ Not executable (chmod +x $script)"39((warnings++))40fi4142# Check 2: Shebang43first_line=$(head -1 "$script")44if [[ ! "$first_line" =~ ^#!/ ]]; then45echo "โ Missing shebang (#!/bin/bash)"46((errors++))47fi4849# Check 3: set -euo pipefail50if ! grep -q "set -euo pipefail" "$script"; then51echo "โ ๏ธ Missing 'set -euo pipefail' (recommended for safety)"52((warnings++))53fi5455# Check 4: Reads from stdin56if ! grep -q "cat\|read" "$script"; then57echo "โ ๏ธ Doesn't appear to read input from stdin"58((warnings++))59fi6061# Check 5: Uses jq for JSON parsing62if grep -q "tool_input\|tool_name" "$script" && ! grep -q "jq" "$script"; then63echo "โ ๏ธ Parses hook input but doesn't use jq"64((warnings++))65fi6667# Check 6: Unquoted variables68if grep -E '\$[A-Za-z_][A-Za-z0-9_]*[^"]' "$script" | grep -v '#' | grep -q .; then69echo "โ ๏ธ Potentially unquoted variables detected (injection risk)"70echo " Always use double quotes: \"\$variable\" not \$variable"71((warnings++))72fi7374# Check 7: Hardcoded paths75if grep -E '^[^#]*/home/|^[^#]*/usr/|^[^#]*/opt/' "$script" | grep -q .; then76echo "โ ๏ธ Hardcoded absolute paths detected"77echo " Use \$CLAUDE_PROJECT_DIR or \$CLAUDE_PLUGIN_ROOT"78((warnings++))79fi8081# Check 8: Uses CLAUDE_PLUGIN_ROOT82if ! grep -q "CLAUDE_PLUGIN_ROOT\|CLAUDE_PROJECT_DIR" "$script"; then83echo "๐ก Tip: Use \$CLAUDE_PLUGIN_ROOT for plugin-relative paths"84fi8586# Check 9: Exit codes87if ! grep -q "exit 0\|exit 2" "$script"; then88echo "โ ๏ธ No explicit exit codes (should exit 0 or 2)"89((warnings++))90fi9192# Check 10: JSON output for decision hooks93if grep -q "PreToolUse\|Stop" "$script"; then94if ! grep -q "permissionDecision\|decision" "$script"; then95echo "๐ก Tip: PreToolUse/Stop hooks should output decision JSON"96fi97fi9899# Check 11: Long-running commands100if grep -E 'sleep [0-9]{3,}|while true' "$script" | grep -v '#' | grep -q .; then101echo "โ ๏ธ Potentially long-running code detected"102echo " Hooks should complete quickly (< 60s)"103((warnings++))104fi105106# Check 12: Error messages to stderr107if grep -q 'echo.*".*error\|Error\|denied\|Denied' "$script"; then108if ! grep -q '>&2' "$script"; then109echo "โ ๏ธ Error messages should be written to stderr (>&2)"110((warnings++))111fi112fi113114# Check 13: Input validation115if ! grep -q "if.*empty\|if.*null\|if.*-z" "$script"; then116echo "๐ก Tip: Consider validating input fields aren't empty"117fi118119echo ""120echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"121122if [ $errors -eq 0 ] && [ $warnings -eq 0 ]; then123echo "โ No issues found"124return 0125elif [ $errors -eq 0 ]; then126echo "โ ๏ธ Found $warnings warning(s)"127return 0128else129echo "โ Found $errors error(s) and $warnings warning(s)"130return 1131fi132}133134echo "๐ Hook Script Linter"135echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"136echo ""137138total_errors=0139140for script in "$@"; do141if ! check_script "$script"; then142((total_errors++))143fi144echo ""145done146147if [ $total_errors -eq 0 ]; then148echo "โ All scripts passed linting"149exit 0150else151echo "โ $total_errors script(s) had errors"152exit 1153fi154