spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / plugins / plugin-dev / skills / hook-development / scripts / hook-linter.sh
153 lines · 4.1 KBRaw
1#!/bin/bash
2# Hook Linter
3# Checks hook scripts for common issues and best practices
4
5set -euo pipefail
6
7# Usage
8if [ $# -eq 0 ]; then
9 echo "Usage: $0 <hook-script.sh> [hook-script2.sh ...]"
10 echo ""
11 echo "Checks hook scripts for:"
12 echo " - Shebang presence"
13 echo " - set -euo pipefail usage"
14 echo " - Input reading from stdin"
15 echo " - Proper error handling"
16 echo " - Variable quoting"
17 echo " - Exit code usage"
18 echo " - Hardcoded paths"
19 echo " - Timeout considerations"
20 exit 1
21fi
22
23check_script() {
24 local script="$1"
25 local warnings=0
26 local errors=0
27
28 echo "🔍 Linting: $script"
29 echo ""
30
31 if [ ! -f "$script" ]; then
32 echo "❌ Error: File not found"
33 return 1
34 fi
35
36 # Check 1: Executable
37 if [ ! -x "$script" ]; then
38 echo "⚠️ Not executable (chmod +x $script)"
39 ((warnings++))
40 fi
41
42 # Check 2: Shebang
43 first_line=$(head -1 "$script")
44 if [[ ! "$first_line" =~ ^#!/ ]]; then
45 echo "❌ Missing shebang (#!/bin/bash)"
46 ((errors++))
47 fi
48
49 # Check 3: set -euo pipefail
50 if ! grep -q "set -euo pipefail" "$script"; then
51 echo "⚠️ Missing 'set -euo pipefail' (recommended for safety)"
52 ((warnings++))
53 fi
54
55 # Check 4: Reads from stdin
56 if ! grep -q "cat\|read" "$script"; then
57 echo "⚠️ Doesn't appear to read input from stdin"
58 ((warnings++))
59 fi
60
61 # Check 5: Uses jq for JSON parsing
62 if grep -q "tool_input\|tool_name" "$script" && ! grep -q "jq" "$script"; then
63 echo "⚠️ Parses hook input but doesn't use jq"
64 ((warnings++))
65 fi
66
67 # Check 6: Unquoted variables
68 if grep -E '\$[A-Za-z_][A-Za-z0-9_]*[^"]' "$script" | grep -v '#' | grep -q .; then
69 echo "⚠️ Potentially unquoted variables detected (injection risk)"
70 echo " Always use double quotes: \"\$variable\" not \$variable"
71 ((warnings++))
72 fi
73
74 # Check 7: Hardcoded paths
75 if grep -E '^[^#]*/home/|^[^#]*/usr/|^[^#]*/opt/' "$script" | grep -q .; then
76 echo "⚠️ Hardcoded absolute paths detected"
77 echo " Use \$CLAUDE_PROJECT_DIR or \$CLAUDE_PLUGIN_ROOT"
78 ((warnings++))
79 fi
80
81 # Check 8: Uses CLAUDE_PLUGIN_ROOT
82 if ! grep -q "CLAUDE_PLUGIN_ROOT\|CLAUDE_PROJECT_DIR" "$script"; then
83 echo "💡 Tip: Use \$CLAUDE_PLUGIN_ROOT for plugin-relative paths"
84 fi
85
86 # Check 9: Exit codes
87 if ! grep -q "exit 0\|exit 2" "$script"; then
88 echo "⚠️ No explicit exit codes (should exit 0 or 2)"
89 ((warnings++))
90 fi
91
92 # Check 10: JSON output for decision hooks
93 if grep -q "PreToolUse\|Stop" "$script"; then
94 if ! grep -q "permissionDecision\|decision" "$script"; then
95 echo "💡 Tip: PreToolUse/Stop hooks should output decision JSON"
96 fi
97 fi
98
99 # Check 11: Long-running commands
100 if grep -E 'sleep [0-9]{3,}|while true' "$script" | grep -v '#' | grep -q .; then
101 echo "⚠️ Potentially long-running code detected"
102 echo " Hooks should complete quickly (< 60s)"
103 ((warnings++))
104 fi
105
106 # Check 12: Error messages to stderr
107 if grep -q 'echo.*".*error\|Error\|denied\|Denied' "$script"; then
108 if ! grep -q '>&2' "$script"; then
109 echo "⚠️ Error messages should be written to stderr (>&2)"
110 ((warnings++))
111 fi
112 fi
113
114 # Check 13: Input validation
115 if ! grep -q "if.*empty\|if.*null\|if.*-z" "$script"; then
116 echo "💡 Tip: Consider validating input fields aren't empty"
117 fi
118
119 echo ""
120 echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
121
122 if [ $errors -eq 0 ] && [ $warnings -eq 0 ]; then
123 echo "✅ No issues found"
124 return 0
125 elif [ $errors -eq 0 ]; then
126 echo "⚠️ Found $warnings warning(s)"
127 return 0
128 else
129 echo "❌ Found $errors error(s) and $warnings warning(s)"
130 return 1
131 fi
132}
133
134echo "🔎 Hook Script Linter"
135echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
136echo ""
137
138total_errors=0
139
140for script in "$@"; do
141 if ! check_script "$script"; then
142 ((total_errors++))
143 fi
144 echo ""
145done
146
147if [ $total_errors -eq 0 ]; then
148 echo "✅ All scripts passed linting"
149 exit 0
150else
151 echo "❌ $total_errors script(s) had errors"
152 exit 1
153fi