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
| 1 | #!/bin/bash |
| 2 | # Hook Testing Helper |
| 3 | # Tests a hook with sample input and shows output |
| 4 | |
| 5 | set -euo pipefail |
| 6 | |
| 7 | # Usage |
| 8 | show_usage() { |
| 9 | echo "Usage: $0 [options] <hook-script> <test-input.json>" |
| 10 | echo "" |
| 11 | echo "Options:" |
| 12 | echo " -h, --help Show this help message" |
| 13 | echo " -v, --verbose Show detailed execution information" |
| 14 | echo " -t, --timeout N Set timeout in seconds (default: 60)" |
| 15 | echo "" |
| 16 | echo "Examples:" |
| 17 | echo " $0 validate-bash.sh test-input.json" |
| 18 | echo " $0 -v -t 30 validate-write.sh write-input.json" |
| 19 | echo "" |
| 20 | echo "Creates sample test input with:" |
| 21 | echo " $0 --create-sample <event-type>" |
| 22 | exit 0 |
| 23 | } |
| 24 | |
| 25 | # Create sample input |
| 26 | create_sample() { |
| 27 | event_type="$1" |
| 28 | |
| 29 | case "$event_type" in |
| 30 | PreToolUse) |
| 31 | cat <<'EOF' |
| 32 | { |
| 33 | "session_id": "test-session", |
| 34 | "transcript_path": "/tmp/transcript.txt", |
| 35 | "cwd": "/tmp/test-project", |
| 36 | "permission_mode": "ask", |
| 37 | "hook_event_name": "PreToolUse", |
| 38 | "tool_name": "Write", |
| 39 | "tool_input": { |
| 40 | "file_path": "/tmp/test.txt", |
| 41 | "content": "Test content" |
| 42 | } |
| 43 | } |
| 44 | EOF |
| 45 | ;; |
| 46 | PostToolUse) |
| 47 | cat <<'EOF' |
| 48 | { |
| 49 | "session_id": "test-session", |
| 50 | "transcript_path": "/tmp/transcript.txt", |
| 51 | "cwd": "/tmp/test-project", |
| 52 | "permission_mode": "ask", |
| 53 | "hook_event_name": "PostToolUse", |
| 54 | "tool_name": "Bash", |
| 55 | "tool_result": "Command executed successfully" |
| 56 | } |
| 57 | EOF |
| 58 | ;; |
| 59 | Stop|SubagentStop) |
| 60 | cat <<'EOF' |
| 61 | { |
| 62 | "session_id": "test-session", |
| 63 | "transcript_path": "/tmp/transcript.txt", |
| 64 | "cwd": "/tmp/test-project", |
| 65 | "permission_mode": "ask", |
| 66 | "hook_event_name": "Stop", |
| 67 | "reason": "Task appears complete" |
| 68 | } |
| 69 | EOF |
| 70 | ;; |
| 71 | UserPromptSubmit) |
| 72 | cat <<'EOF' |
| 73 | { |
| 74 | "session_id": "test-session", |
| 75 | "transcript_path": "/tmp/transcript.txt", |
| 76 | "cwd": "/tmp/test-project", |
| 77 | "permission_mode": "ask", |
| 78 | "hook_event_name": "UserPromptSubmit", |
| 79 | "user_prompt": "Test user prompt" |
| 80 | } |
| 81 | EOF |
| 82 | ;; |
| 83 | SessionStart|SessionEnd) |
| 84 | cat <<'EOF' |
| 85 | { |
| 86 | "session_id": "test-session", |
| 87 | "transcript_path": "/tmp/transcript.txt", |
| 88 | "cwd": "/tmp/test-project", |
| 89 | "permission_mode": "ask", |
| 90 | "hook_event_name": "SessionStart" |
| 91 | } |
| 92 | EOF |
| 93 | ;; |
| 94 | *) |
| 95 | echo "Unknown event type: $event_type" |
| 96 | echo "Valid types: PreToolUse, PostToolUse, Stop, SubagentStop, UserPromptSubmit, SessionStart, SessionEnd" |
| 97 | exit 1 |
| 98 | ;; |
| 99 | esac |
| 100 | } |
| 101 | |
| 102 | # Parse arguments |
| 103 | VERBOSE=false |
| 104 | TIMEOUT=60 |
| 105 | |
| 106 | while [ $# -gt 0 ]; do |
| 107 | case "$1" in |
| 108 | -h|--help) |
| 109 | show_usage |
| 110 | ;; |
| 111 | -v|--verbose) |
| 112 | VERBOSE=true |
| 113 | shift |
| 114 | ;; |
| 115 | -t|--timeout) |
| 116 | TIMEOUT="$2" |
| 117 | shift 2 |
| 118 | ;; |
| 119 | --create-sample) |
| 120 | create_sample "$2" |
| 121 | exit 0 |
| 122 | ;; |
| 123 | *) |
| 124 | break |
| 125 | ;; |
| 126 | esac |
| 127 | done |
| 128 | |
| 129 | if [ $# -ne 2 ]; then |
| 130 | echo "Error: Missing required arguments" |
| 131 | echo "" |
| 132 | show_usage |
| 133 | fi |
| 134 | |
| 135 | HOOK_SCRIPT="$1" |
| 136 | TEST_INPUT="$2" |
| 137 | |
| 138 | # Validate inputs |
| 139 | if [ ! -f "$HOOK_SCRIPT" ]; then |
| 140 | echo "❌ Error: Hook script not found: $HOOK_SCRIPT" |
| 141 | exit 1 |
| 142 | fi |
| 143 | |
| 144 | if [ ! -x "$HOOK_SCRIPT" ]; then |
| 145 | echo "⚠️ Warning: Hook script is not executable. Attempting to run with bash..." |
| 146 | HOOK_SCRIPT="bash $HOOK_SCRIPT" |
| 147 | fi |
| 148 | |
| 149 | if [ ! -f "$TEST_INPUT" ]; then |
| 150 | echo "❌ Error: Test input not found: $TEST_INPUT" |
| 151 | exit 1 |
| 152 | fi |
| 153 | |
| 154 | # Validate test input JSON |
| 155 | if ! jq empty "$TEST_INPUT" 2>/dev/null; then |
| 156 | echo "❌ Error: Test input is not valid JSON" |
| 157 | exit 1 |
| 158 | fi |
| 159 | |
| 160 | echo "🧪 Testing hook: $HOOK_SCRIPT" |
| 161 | echo "📥 Input: $TEST_INPUT" |
| 162 | echo "" |
| 163 | |
| 164 | if [ "$VERBOSE" = true ]; then |
| 165 | echo "Input JSON:" |
| 166 | jq . "$TEST_INPUT" |
| 167 | echo "" |
| 168 | fi |
| 169 | |
| 170 | # Set up environment |
| 171 | export CLAUDE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-/tmp/test-project}" |
| 172 | export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(pwd)}" |
| 173 | export CLAUDE_ENV_FILE="${CLAUDE_ENV_FILE:-/tmp/test-env-$$}" |
| 174 | |
| 175 | if [ "$VERBOSE" = true ]; then |
| 176 | echo "Environment:" |
| 177 | echo " CLAUDE_PROJECT_DIR=$CLAUDE_PROJECT_DIR" |
| 178 | echo " CLAUDE_PLUGIN_ROOT=$CLAUDE_PLUGIN_ROOT" |
| 179 | echo " CLAUDE_ENV_FILE=$CLAUDE_ENV_FILE" |
| 180 | echo "" |
| 181 | fi |
| 182 | |
| 183 | # Run the hook |
| 184 | echo "▶️ Running hook (timeout: ${TIMEOUT}s)..." |
| 185 | echo "" |
| 186 | |
| 187 | start_time=$(date +%s) |
| 188 | |
| 189 | set +e |
| 190 | output=$(timeout "$TIMEOUT" bash -c "cat '$TEST_INPUT' | $HOOK_SCRIPT" 2>&1) |
| 191 | exit_code=$? |
| 192 | set -e |
| 193 | |
| 194 | end_time=$(date +%s) |
| 195 | duration=$((end_time - start_time)) |
| 196 | |
| 197 | # Analyze results |
| 198 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" |
| 199 | echo "Results:" |
| 200 | echo "" |
| 201 | echo "Exit Code: $exit_code" |
| 202 | echo "Duration: ${duration}s" |
| 203 | echo "" |
| 204 | |
| 205 | case $exit_code in |
| 206 | 0) |
| 207 | echo "✅ Hook approved/succeeded" |
| 208 | ;; |
| 209 | 2) |
| 210 | echo "🚫 Hook blocked/denied" |
| 211 | ;; |
| 212 | 124) |
| 213 | echo "⏱️ Hook timed out after ${TIMEOUT}s" |
| 214 | ;; |
| 215 | *) |
| 216 | echo "⚠️ Hook returned unexpected exit code: $exit_code" |
| 217 | ;; |
| 218 | esac |
| 219 | |
| 220 | echo "" |
| 221 | echo "Output:" |
| 222 | if [ -n "$output" ]; then |
| 223 | echo "$output" |
| 224 | echo "" |
| 225 | |
| 226 | # Try to parse as JSON |
| 227 | if echo "$output" | jq empty 2>/dev/null; then |
| 228 | echo "Parsed JSON output:" |
| 229 | echo "$output" | jq . |
| 230 | fi |
| 231 | else |
| 232 | echo "(no output)" |
| 233 | fi |
| 234 | |
| 235 | # Check for environment file |
| 236 | if [ -f "$CLAUDE_ENV_FILE" ]; then |
| 237 | echo "" |
| 238 | echo "Environment file created:" |
| 239 | cat "$CLAUDE_ENV_FILE" |
| 240 | rm -f "$CLAUDE_ENV_FILE" |
| 241 | fi |
| 242 | |
| 243 | echo "" |
| 244 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" |
| 245 | |
| 246 | if [ $exit_code -eq 0 ] || [ $exit_code -eq 2 ]; then |
| 247 | echo "✅ Test completed successfully" |
| 248 | exit 0 |
| 249 | else |
| 250 | echo "❌ Test failed" |
| 251 | exit 1 |
| 252 | fi |