The example uses external hook scripts in the hook_scripts/ directory:
block_dangerous.sh - PreToolUse hook
#!/bin/bash# PreToolUse hook: Block dangerous rm -rf commands# Uses jq for JSON parsing (needed for nested fields like tool_input.command)input=$(cat)command=$(echo "$input" | jq -r '.tool_input.command // ""')# Block rm -rf commandsif [[ "$command" =~ "rm -rf" ]]; then echo '{"decision": "deny", "reason": "rm -rf commands are blocked for safety"}' exit 2 # Exit code 2 = block the operationfiexit 0 # Exit code 0 = allow the operation
log_tools.sh - PostToolUse hook
#!/bin/bash# PostToolUse hook: Log all tool usage# Uses OPENHANDS_TOOL_NAME env var (no jq/python needed!)# LOG_FILE should be set by the calling scriptLOG_FILE="${LOG_FILE:-/tmp/tool_usage.log}"echo "[$(date)] Tool used: $OPENHANDS_TOOL_NAME" >> "$LOG_FILE"exit 0
inject_git_context.sh - UserPromptSubmit hook
#!/bin/bash# UserPromptSubmit hook: Inject git status when user asks about code changesinput=$(cat)# Check if user is asking about changes, diff, or gitif echo "$input" | grep -qiE "(changes|diff|git|commit|modified)"; then # Get git status if in a git repo if git rev-parse --git-dir > /dev/null 2>&1; then status=$(git status --short 2>/dev/null | head -10) if [ -n "$status" ]; then # Escape for JSON escaped=$(echo "$status" | sed 's/"/\\"/g' | tr '\n' ' ') echo "{\"additionalContext\": \"Current git status: $escaped\"}" fi fifiexit 0
require_summary.sh - Stop hook
#!/bin/bash# Stop hook: Require a summary.txt file before allowing agent to finish# SUMMARY_FILE should be set by the calling scriptSUMMARY_FILE="${SUMMARY_FILE:-./summary.txt}"if [ ! -f "$SUMMARY_FILE" ]; then echo '{"decision": "deny", "additionalContext": "Create summary.txt first."}' exit 2fiexit 0
Running the Example
export LLM_API_KEY="your-api-key"cd agent-sdkuv run python examples/01_standalone_sdk/33_hooks/33_hooks.py