#!/usr/bin/env ruby # frozen_string_literal: true require 'claude_hooks' # PostToolUse Handler # # PURPOSE: Process and analyze tool execution results # TRIGGERS: After Claude Code executes any tool (Bash, Write, Edit, etc.) # # COMMON USE CASES: # - Parse and format tool output # - Extract errors and warnings from command results # - Update project state based on tool results # - Log execution metrics and performance data # - Trigger follow-up actions based on results # - Cache or store important tool outputs # # SETTINGS.JSON CONFIGURATION: # { # "hooks": { # "PostToolUse": [{ # "matcher": "", # "hooks": [{ # "type": "command", # "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/entrypoints/post_tool_use.rb" # }] # }] # } # } class PostToolUseHandler < ClaudeHooks::PostToolUse def call log "Processing tool result: #{tool_name}" # Example: Analyze tool results # analyze_tool_results # Example: Extract errors and warnings # extract_errors_and_warnings # Example: Update project state # update_project_state # Example: Log execution metrics # log_execution_metrics output_data end private def analyze_tool_results case tool_name when 'Bash' # Analyze command execution results analyze_bash_results when 'Write', 'Edit', 'MultiEdit' # Track file modifications analyze_file_modifications when 'Grep', 'Glob' # Analyze search results analyze_search_results end end def analyze_bash_results return unless tool_result.is_a?(Hash) exit_code = tool_result['exit_code'] || 0 stdout = tool_result['stdout'] || '' stderr = tool_result['stderr'] || '' if exit_code != 0 log "Command failed with exit code: #{exit_code}", level: :error log "stderr: #{stderr}", level: :error if stderr && !stderr.empty? else log 'Command executed successfully' end # Example: Parse specific command outputs command = tool_input['command'] || '' if command.include?('npm test') || command.include?('yarn test') parse_test_results(stdout) elsif command.include?('git status') parse_git_status(stdout) elsif command.include?('npm install') || command.include?('yarn install') parse_install_results(stdout, stderr) end end def analyze_file_modifications file_path = tool_input['file_path'] || '' log "File modified: #{file_path}" # Example: Track important file changes if file_path.end_with?('package.json') log 'Package.json modified - dependencies may have changed' elsif file_path.end_with?('.env') log 'Environment file modified', level: :warn elsif file_path.match?(/\.(js|ts|jsx|tsx)$/) log 'Source code file modified' end end def analyze_search_results return unless tool_result.is_a?(Hash) results = tool_result['results'] || [] log "Search returned #{results.length} results" # Example: Log interesting search patterns if results.length > 100 log 'Large search result set - consider narrowing search', level: :warn elsif results.empty? log 'No search results found' end end def extract_errors_and_warnings return unless tool_result.is_a?(Hash) output_text = [ tool_result['stdout'], tool_result['stderr'] ].compact.join("\n") # Extract common error patterns error_patterns = [ /error:/i, /exception:/i, /failed:/i, /cannot find/i, /permission denied/i ] warning_patterns = [ /warning:/i, /deprecated:/i, /caution:/i ] error_patterns.each do |pattern| if output_text.match?(pattern) log 'Error detected in tool output', level: :error break end end warning_patterns.each do |pattern| if output_text.match?(pattern) log 'Warning detected in tool output', level: :warn break end end end def update_project_state # Example: Update cached project information based on tool results case tool_name when 'Bash' command = tool_input['command'] || '' if command.include?('git checkout') && tool_result['exit_code'].zero? log 'Git branch changed - project state updated' elsif command.include?('npm install') && tool_result['exit_code'].zero? log 'Dependencies installed - project state updated' end end end def log_execution_metrics # Example: Log performance and usage metrics return unless tool_result.is_a?(Hash) duration = tool_result['duration_ms'] log "Tool execution time: #{duration}ms" if duration # Track resource usage return unless tool_name == 'Bash' command = tool_input['command'] || '' log "Executed command: #{command[0..50]}#{'...' if command.length > 50}" end def parse_test_results(stdout) if stdout.include?('passing') || stdout.include?('✓') log 'Tests appear to be passing' elsif stdout.include?('failing') || stdout.include?('✗') log 'Tests appear to be failing', level: :warn end end def parse_git_status(stdout) if stdout.include?('nothing to commit') log 'Git working directory is clean' elsif stdout.include?('Changes not staged') log 'Unstaged changes detected' end end def parse_install_results(stdout, stderr) if stderr&.include?('WARN') log 'Package installation completed with warnings', level: :warn elsif stdout&.include?('added') log 'Packages installed successfully' end end end # Testing support - run this file directly to test with sample data if __FILE__ == $PROGRAM_NAME ClaudeHooks::CLI.test_runner(PostToolUseHandler) do |input_data| input_data['tool_name'] = 'Bash' input_data['tool_input'] = { 'command' => 'npm test' } input_data['tool_result'] = { 'exit_code' => 0, 'stdout' => 'All tests passing' } input_data['session_id'] = 'test-session-01' end end