conversationId.sourcestartup - First-time startup (currently the only supported value){"session_id": "abc123","transcript_path": "/path/to/transcript.txt","cwd": "/project/path","hook_event_name": "SessionStart","source": "startup"}
{"continue": true,"hookSpecificOutput": {"hookEventName": "SessionStart","additionalContext": "The project uses TypeScript + React. Prioritize using function components."}}
{"hooks": {"SessionStart": [{"matcher": "startup","hooks": [{"type": "command","command": "/path/to/session_start.py","timeout": 30}]}]}}
reasonother - Session ended (currently only this value is supported, including scenarios such as switching sessions, deleting sessions, and clearing sessions){"session_id": "abc123","transcript_path": "/path/to/transcript.txt","cwd": "/project/path","hook_event_name": "SessionEnd","reason": "other"}
{"continue": true,"systemMessage": "The session has been cleaned up and temporary files have been deleted."}
tool_nameexecute_command, write_to_file, read_filewrite_to_file|replace_in_file* or an empty string.{"session_id": "abc123","transcript_path": "/path/to/transcript.txt","cwd": "/project/path","hook_event_name": "PreToolUse","tool_name": "execute_command","tool_input": {"command": "npm install","requires_approval": false}}
{"continue": true,"hookSpecificOutput": {"hookEventName": "PreToolUse","permissionDecision": "allow"}}
{"continue": true,"hookSpecificOutput": {"hookEventName": "PreToolUse","permissionDecision": "allow","permissionDecisionReason": "Added the --legacy-peer-deps parameter","modifiedInput": {"command": "npm install --legacy-peer-deps","requires_approval": false}}}
{"continue": false,"hookSpecificOutput": {"hookEventName": "PreToolUse","permissionDecision": "deny","permissionDecisionReason": "Dangerous command detected: rm -rf /"}}
{"continue": true,"hookSpecificOutput": {"hookEventName": "PreToolUse","permissionDecision": "ask","permissionDecisionReason": "git push --force detected. Continue?"}}
tool_name{"session_id": "abc123","transcript_path": "/path/to/transcript.txt","cwd": "/project/path","hook_event_name": "PostToolUse","tool_name": "execute_command","tool_input": {"command": "npm test"},"tool_response": {"exitCode": 0,"stdout": "All tests passed","stderr": ""}}
{"continue": true,"hookSpecificOutput": {"hookEventName": "PostToolUse","additionalContext": "Tests have passed. You can continue development."}}
{"session_id": "abc123","transcript_path": "/path/to/transcript.txt","cwd": "/project/path","hook_event_name": "UserPromptSubmit","prompt": "Help me implement a login feature"}
{"continue": true,"hookSpecificOutput": {"hookEventName": "UserPromptSubmit","additionalContext": "Note: The project has already integrated the JWT authentication library. It is recommended to use it."}}
{"continue": false,"stopReason": "Input contains sensitive information and has been blocked"}
{"session_id": "abc123","transcript_path": "/path/to/transcript.txt","cwd": "/project/path","hook_event_name": "Stop","stop_hook_active": false}
{"continue": false,"stopReason": "Please verify whether the code has passed the unit tests"}
triggermanual - The user manually triggers /summarizeauto - Automatic compaction{"session_id": "abc123","transcript_path": "/path/to/transcript.txt","cwd": "/project/path","hook_event_name": "PreCompact","trigger": "auto","custom_instructions": "Keep all discussions related to API design"}
{"continue": true,"hookSpecificOutput": {"hookEventName": "PreCompact","additionalContext": "Important: Keep the database table schema design"}}
{"session_id": "Session ID","transcript_path": "Path to the conversation transcript file","cwd": "Current working directory","hook_event_name": "Event name"}
SessionStart: sourceSessionEnd: reasonPreToolUse/PostToolUse: tool_name, tool_input, tool_responseUserPromptSubmit: promptPreCompact: trigger, custom_instructionsStop: stop_hook_active{"continue": true,"suppressOutput": false,"systemMessage": "Optional system message","stopReason": "The reason for blocking (when continue=false)","hookSpecificOutput": {"hookEventName": "Event name","permissionDecision": "allow|deny|ask","permissionDecisionReason": "Decision reason","modifiedInput": {},"additionalContext": "Additional context"}}
continue: Whether to allow the operation to continue (false means to block it)suppressOutput: Whether to hide stdout outputsystemMessage: The system message displayed to the userstopReason: The reason for blockinghookSpecificOutput: Event-specific output dataExit Code | Description | Action |
0 | Executed successfully | Allows the operation to continue. stdout may be processed. |
1 | Non-blocking error | Display stderr as a warning and allow the operation to continue. |
2 | Blocking error | Block the operation. stderr is passed to the Agent/model. |
Other | Non-blocking error | Same as exit code 1. |
CLAUDE_PROJECT_DIR: The project root directory (compatible with Claude Code)CODEBUDDY_PROJECT_DIR: The project root directory (specific to CodeBuddy)<workspace>/.codebuddy/settings.json~/.codebuddy/settings.json{"hooks": {"PreToolUse": [{"matcher": "execute_command","hooks": [{"type": "command","command": "/absolute/path/to/script.py","timeout": 10}]},{"matcher": "write_to_file|replace_in_file","hooks": [{"type": "command","command": "/path/to/backup_script.sh","timeout": 20}]}],"SessionStart": [{"matcher": "startup","hooks": [{"type": "command","command": "/path/to/init.py","timeout": 30}]}]}}
"" or "*": matches all."execute_command""write_to_file|replace_in_file""read.*|search.*"PreToolUse/PostToolUse: matches tool_nameSessionStart: matches sourceSessionEnd: matches reasonPreCompact: matches triggerUserPromptSubmit/Stop: Do not use matcher"$CODEBUDDY_PROJECT_DIR/.codebuddy/hooks/script.py""python3 /path/to/script.py"60 secondsrm -rf commandsvalidate_command.py):#!/usr/bin/env python3import jsonimport sysDANGEROUS_COMMANDS = ['rm -rf /', 'dd if=/dev/zero', 'mkfs']def main():input_data = json.loads(sys.stdin.read())if input_data.get('tool_name') != 'execute_command':print(json.dumps({"continue": True}))return 0command = input_data.get('tool_input', {}).get('command', '')for dangerous in DANGEROUS_COMMANDS:if dangerous in command:output = {"continue": False,"hookSpecificOutput": {"hookEventName": "PreToolUse","permissionDecision": "deny","permissionDecisionReason": f"Dangerous command detected: {dangerous}"}}print(json.dumps(output, ensure_ascii=False))return 0print(json.dumps({"continue": True}))return 0if __name__ == "__main__":sys.exit(main())
{"hooks": {"PreToolUse": [{"matcher": "execute_command","hooks": [{"type": "command","command": "/path/to/validate_command.py","timeout": 10}]}]}}
--legacy-peer-deps parameter to npm installmodify_npm.py):#!/usr/bin/env python3import jsonimport sysimport redef main():input_data = json.loads(sys.stdin.read())if input_data.get('tool_name') != 'execute_command':print(json.dumps({"continue": True}))return 0tool_input = input_data.get('tool_input', {})command = tool_input.get('command', '')# Check whether it is npm installif re.match(r'^npm\\s+(i|install)\\b', command.strip()):# If --legacy-peer-deps is not present, add itif '--legacy-peer-deps' not in command:modified_command = command.strip() + ' --legacy-peer-deps'output = {"continue": True,"hookSpecificOutput": {"hookEventName": "PreToolUse","permissionDecision": "allow","permissionDecisionReason": "Automatically added the --legacy-peer-deps parameter","modifiedInput": {"command": modified_command,"requires_approval": tool_input.get('requires_approval', False)}}}print(json.dumps(output, ensure_ascii=False))return 0print(json.dumps({"continue": True}))return 0if __name__ == "__main__":sys.exit(main())
backup_files.py):#!/usr/bin/env python3import jsonimport sysimport osimport shutilfrom datetime import datetimedef main():input_data = json.loads(sys.stdin.read())tool_name = input_data.get('tool_name', '')# Only process file write tools.if tool_name not in ['write_to_file', 'replace_in_file']:print(json.dumps({"continue": True}))return 0tool_input = input_data.get('tool_input', {})file_path = tool_input.get('filePath')if not file_path or not os.path.exists(file_path):print(json.dumps({"continue": True}))return 0# Create a backup directory.project_dir = os.environ.get('CODEBUDDY_PROJECT_DIR', '')backup_dir = os.path.join(project_dir, '.codebuddy', 'backups')os.makedirs(backup_dir, exist_ok=True)# Generate a backup file name.timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')backup_name = f"{os.path.basename(file_path)}.{timestamp}.bak"backup_path = os.path.join(backup_dir, backup_name)# Create a backup.shutil.copy2(file_path, backup_path)output = {"continue": True,"systemMessage": f"Backed up to: {backup_path}"}print(json.dumps(output, ensure_ascii=False))return 0if __name__ == "__main__":sys.exit(main())
{"hooks": {"PreToolUse": [{"matcher": "write_to_file|replace_in_file","hooks": [{"type": "command","command": "/path/to/backup_files.py","timeout": 15}]}]}}
session_start.py):#!/usr/bin/env python3import jsonimport sysimport osdef main():input_data = json.loads(sys.stdin.read())project_dir = os.environ.get('CODEBUDDY_PROJECT_DIR', '')# Load project configuration.config_file = os.path.join(project_dir, '.codebuddy', 'project.json')project_info = ""if os.path.exists(config_file):with open(config_file, 'r') as f:config = json.load(f)project_info = f"""Project name: {config.get('name', 'Unknown')}Tech stack: {', '.join(config.get('tech_stack', []))}Coding standard: {config.get('coding_standard', 'Standard')}"""output = {"continue": True,"hookSpecificOutput": {"hookEventName": "SessionStart","additionalContext": f"""Session started!Project directory: {project_dir}Launch source: {input_data.get('source', 'unknown')}{project_info}"""}}print(json.dumps(output, ensure_ascii=False))return 0if __name__ == "__main__":sys.exit(main())
save_context.py):#!/usr/bin/env python3import jsonimport sysimport osimport shutilfrom datetime import datetimedef main():input_data = json.loads(sys.stdin.read())# Process automatic compaction only.if input_data.get('trigger') != 'auto':print(json.dumps({"continue": True}))return 0project_dir = os.environ.get('CODEBUDDY_PROJECT_DIR', '')transcript_path = input_data.get('transcript_path', '')if not transcript_path or not os.path.exists(transcript_path):print(json.dumps({"continue": True}))return 0# Create a save directory.save_dir = os.path.join(project_dir, '.codebuddy', 'context_history')os.makedirs(save_dir, exist_ok=True)# Save the conversation history.timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')save_path = os.path.join(save_dir, f'transcript_{timestamp}.txt')shutil.copy2(transcript_path, save_path)output = {"continue": True,"systemMessage": f"Context saved to: {save_path}"}print(json.dumps(output, ensure_ascii=False))return 0if __name__ == "__main__":sys.exit(main())
{"hooks": {"PreCompact": [{"matcher": "auto","hooks": [{"type": "command","command": "/path/to/save_context.py","timeout": 20}]}]}}
mkdir -p ~/.codebuddycat > ~/.codebuddy/settings.json << 'EOF'{"hooks": {"SessionStart": [{"matcher": "startup","hooks": [{"type": "command","command": "/usr/bin/env python3 -c \\"import json,sys; print(json.dumps({'continue': True, 'hookSpecificOutput': {'hookEventName': 'SessionStart', 'additionalContext': 'Hook configured successfully!'}}))\\"","timeout": 5}]}]}}EOF
# Create a directory for Hook scripts.mkdir -p ~/.codebuddy/hooks# Create a test script.cat > ~/.codebuddy/hooks/my_first_hook.py << 'EOF'#!/usr/bin/env python3import jsonimport sysdef main():input_data = json.loads(sys.stdin.read())output = {"continue": True,"hookSpecificOutput": {"hookEventName": "SessionStart","additionalContext": f"Welcome to Agent-Craft! Current project: {input_data.get('cwd', 'unknown')}"}}print(json.dumps(output, ensure_ascii=False))return 0if __name__ == "__main__":sys.exit(main())EOF# Add execute permission.chmod +x ~/.codebuddy/hooks/my_first_hook.py
{"hooks": {"SessionStart": [{"matcher": "startup","hooks": [{"type": "command","command": "/Users/YOUR_USERNAME/.codebuddy/hooks/my_first_hook.py","timeout": 10}]}]}}
import sysdef debug_log(message):"""Write debug logs without affecting stdout."""with open('/tmp/hook_debug.log', 'a') as f:f.write(f"{message}\\n")# Use in Hook scripts.debug_log(f"Received input: {json.dumps(input_data)}")
# Test whether the JSON output by the script is valid.echo '{"hook_event_name":"SessionStart"}' | python3 your_hook.py | jq .
# View Hook logs in real time.tail -f ~/.codebuddy/logs/agent-craft.log | grep -i hook
import os# Obtain the project directory in a Hook.project_dir = os.environ.get('CODEBUDDY_PROJECT_DIR', '')claude_dir = os.environ.get('CLAUDE_PROJECT_DIR', '') # Compatible with Claude Code
ALLOWED_COMMANDS = ['npm install','npm test','git status','git diff']def is_allowed(command):return any(command.startswith(allowed) for allowed in ALLOWED_COMMANDS)
def enhance_command(command):Automatically add common parameters.enhancements = {'npm install': ' --legacy-peer-deps','git push': ' --dry-run', # Safe mode}for prefix, suffix in enhancements.items():if command.startswith(prefix) and suffix not in command:return command + suffixreturn command
def should_block(input_data):Determine whether to block based on multiple conditions.tool_name = input_data.get('tool_name')tool_input = input_data.get('tool_input', {})# Rule 1: Prevent deletion of important filesif tool_name == 'delete_files':file_path = tool_input.get('target_file', '')if any(important in file_path for important in ['.git', 'package.json']):return True, "Important files cannot be deleted"# Rule 2: Block dangerous commandsif tool_name == 'execute_command':command = tool_input.get('command', '')if 'rm -rf /' in command or 'dd if=' in command:return True, "Dangerous command detected"return False, None
{"hooks": {"SessionStart": [{"matcher": "startup","hooks": [{"type": "command","command": "node ~/.codebuddy/hooks/nodejs-init.js","timeout": 15}]}],"PreToolUse": [{"matcher": "execute_command","hooks": [{"type": "command","command": "python3 ~/.codebuddy/hooks/npm-safety-check.py","timeout": 5}]}]}}
{"hooks": {"SessionStart": [{"matcher": "startup","hooks": [{"type": "command","command": "python3 ~/.codebuddy/hooks/python-env-check.py","timeout": 10}]}],"PostToolUse": [{"matcher": "write_to_file|replace_in_file","hooks": [{"type": "command","command": "python3 ~/.codebuddy/hooks/python-lint.py","timeout": 20}]}]}}
sys.stderr to output debug information and avoid polluting stdout.echo '{"hook_event_name":"PreToolUse","tool_name":"execute_command","tool_input":{"command":"npm install"}}' | \\python3 /path/to/your_hook.py
# Output debug information in Hook scripts.import syssys.stderr.write(f"[DEBUG] Processing command: {command}\\n")sys.stderr.flush()
<workspace>/.codebuddy/ and manage them with project version control.~/.codebuddy/ for reuse across projects.# Bad Practice: Loading Large Files Every Timedef main():with open('huge_config.json', 'r') as f:config = json.load(f) # Read every time# ... Processing logic# Good Practice: Cache ConfigurationsCONFIG_CACHE = Nonedef get_config():global CONFIG_CACHEif CONFIG_CACHE is None:with open('huge_config.json', 'r') as f:CONFIG_CACHE = json.load(f)return CONFIG_CACHE
{"hooks": {"PreToolUse": [{"matcher": "execute_command","hooks": [{"type": "command","command": "/path/to/fast_check.sh","timeout": 3}]},{"matcher": ".*","hooks": [{"type": "command","command": "/path/to/general_check.py","timeout": 10}]}]}}
import fcntldef safe_append_log(message):"""Thread-safe log writing"""with open('/tmp/hook.log', 'a') as f:fcntl.flock(f.fileno(), fcntl.LOCK_EX)f.write(message + '\\n')fcntl.flock(f.fileno(), fcntl.LOCK_UN)
def validate_input(input_data):"""Verify the integrity of input data."""required_fields = ['hook_event_name', 'session_id']for field in required_fields:if field not in input_data:raise ValueError(f"Missing required field: {field}")# Verify Field Typesif not isinstance(input_data.get('tool_input'), dict):raise ValueError("tool_input must be a dictionary")return True
# ❌ Danger: Direct Executionos.system(f"echo {user_input}")# ✅ Safe: Use Parameterizationimport subprocesssubprocess.run(['echo', user_input], check=True)
import osdef safe_file_access(file_path, project_dir):"""Ensure that file paths are within the project directory."""abs_path = os.path.abspath(file_path)abs_project = os.path.abspath(project_dir)if not abs_path.startswith(abs_project):raise ValueError("Path traversal detected")return abs_path
# Hook scripts should run with the minimum necessary permissions.# Avoid using sudo or root privileges.# ✅ Check Permissionsif os.geteuid() == 0:print("Warning: Running as root is not recommended", file=sys.stderr)
def main():input_data = json.loads(sys.stdin.read())# Process only under specific conditions.if not should_process(input_data):print(json.dumps({"continue": True}))return 0# Execute Processing Logic...
def main():input_data = json.loads(sys.stdin.read())# Apply Multiple Rulesfor rule in RULES:if rule.matches(input_data):return rule.apply(input_data)# Default Behaviorprint(json.dumps({"continue": True}))return 0
import requestsdef check_with_external_service(command):response = requests.post('https://api.example.com/validate',json={'command': command},timeout=5)return response.json()
settings.json is in the .codebuddy directory).hooks field is configured correctly, and the JSON format is valid.matcher regular expression can match the target.chmod +x script.py).#!/usr/bin/env python3).timeout configuration value.echo to manually pass in test data.sys.stderr in the script to output debug information.modifiedInput field is returned.permissionDecision is allow.continue is true.conversationId.interface HookInput {// Common Fieldssession_id?: string; // Session IDtranscript_path?: string; // Path to the conversation transcriptcwd?: string; // Current working directoryhook_event_name: string; // Hook event name// Dedicated to SessionStart (currently only 'startup' is supported)source?: 'startup';// Dedicated to UserPromptSubmitprompt?: string; // User input content// Dedicated to PreToolUse/PostToolUsetool_name?: string; // Tool nametool_input?: Record<string, any>; // Tool input parameterstool_response?: any; // Tool response (PostToolUse only)// Dedicated to Stopstop_hook_active?: boolean; // Whether the Stop Hook is activated// Dedicated to PreCompacttrigger?: 'manual' | 'auto'; // Trigger methodcustom_instructions?: string; // Custom compression instructions}
interface HookOutput {// Basic controlcontinue?: boolean; // Whether to continue execution (defaults to true)stopReason?: string; // Stop reasonsuppressOutput?: boolean; // Whether to suppress outputsystemMessage?: string; // System message// Hook-specific outputhookSpecificOutput?: {hookEventName: string; // Hook event name// Dedicated to PreToolUsepermissionDecision?: 'allow' | 'deny' | 'ask';permissionDecisionReason?: string;modifiedInput?: Record<string, any>;// Dedicated to SessionStart, UserPromptSubmit, and PostToolUseadditionalContext?: string; // Additional context};}
Environment Variable | Description | Example Value |
CODEBUDDY_PROJECT_DIR | Project root directory | /path/to/project |
CLAUDE_PROJECT_DIR | Project root directory (Claude Code compatible) | /path/to/project |
Exit Code | Description | stdout | stderr | Action |
0 | Successful | Process as result | Ignored. | Continue execution and may inject into the context. |
1 | Warning | Ignored. | Display as a warning | Continue execution |
2 | Block/Feedback | Ignored. | Pass to Agent | PreToolUse: Block execution <br>Stop: Provide feedback |
Other | Error | Ignored. | Display as a warning | Continue execution |
read_file - Read a filewrite_to_file - Write to a filereplace_in_file - Replace file contentdelete_files - Delete fileslist_files - List filessearch_file - Search for filessearch_content - Search for contentread_lints - Read Lint errorsexecute_command - Run the commandpreview_url - Preview URLtask - Create a subtaskweb_search - Web searchweb_fetch - Fetch web contentask_followup_question - Ask the userWas this page helpful?
You can also Contact sales or Submit a Ticket for help.
Help us improve! Rate your documentation experience in 5 mins.
Feedback