--- name: hook-development description: This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "wire command hooks", "use ${PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStart, SubagentStop, SessionStart, UserPromptSubmit). Provides comprehensive guidance for creating and implementing DeepSeek Harness plugin hooks with focus on the command-hook contract and the matcher model. --- # Hook Development for DeepSeek Harness Plugins ## Overview Hooks are event-driven automation scripts that execute in response to DeepSeek Harness events. Use hooks to validate operations, enforce policies, add context, and integrate external tools into workflows. **Key capabilities:** - Validate tool calls before execution (PreToolUse) - React to tool results (PostToolUse) - Enforce completion standards (Stop, SubagentStop) - Load project context (SessionStart) - Automate workflows across the development lifecycle ## Hook Types ### Command Hooks (The Supported Type) Execute bash commands for deterministic checks: ```json { "type": "command", "command": "bash ${PLUGIN_ROOT}/scripts/validate.sh", "timeout": 60 } ``` **Use for:** - Fast deterministic validations - File system operations - External tool integrations - Performance-critical checks ## Hook Configuration Formats ### Plugin hooks.json Format **For plugin hooks** in `com.deepseek.harness/hooks/hooks.json`, use wrapper format: ```json { "description": "Brief explanation of hooks (optional)", "hooks": { "PreToolUse": [...], "Stop": [...], "SessionStart": [...] } } ``` **Key points:** - `description` field is optional - `hooks` field is required wrapper containing actual hook events - This is the **plugin-specific format** **Example:** ```json { "description": "Validation hooks for code quality", "hooks": { "PreToolUse": [ { "matcher": "Write", "hooks": [ { "type": "command", "command": "${PLUGIN_ROOT}/hooks/validate.sh" } ] } ] } } ``` ### Settings Format (Direct) **For a standalone hook file** (for example a project `.agents/hooks/hooks.json`), use the bare event-table format: ```json { "PreToolUse": [...], "Stop": [...], "SessionStart": [...] } ``` **Key points:** - No wrapper - events directly at top level - No description field - This is the **settings format** **Important:** The examples below show the hook event structure that goes inside either format. For plugin hooks.json, wrap these in `{"hooks": {...}}`. ## Hook Events ### PreToolUse Execute before any tool runs. Use to approve, deny, or modify tool calls. **Example:** ```json { "PreToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "bash ${PLUGIN_ROOT}/scripts/validate-write.sh", "timeout": 10 } ] } ] } ``` **Output for PreToolUse:** ```json { "hookSpecificOutput": { "permissionDecision": "allow|deny|ask", "updatedInput": {"field": "modified_value"} }, "systemMessage": "Explanation for the agent" } ``` ### PostToolUse Execute after tool completes. Use to react to results, provide feedback, or log. **Example:** ```json { "PostToolUse": [ { "matcher": "Edit", "hooks": [ { "type": "command", "command": "bash ${PLUGIN_ROOT}/scripts/check-edit-result.sh", "timeout": 10 } ] } ] } ``` **Output behavior:** - Exit 0: stdout shown in transcript - Exit 2: stderr fed back to the agent - systemMessage included in context ### Stop Execute when main agent considers stopping. Use to validate completeness. **Example:** ```json { "Stop": [ { "hooks": [ { "type": "command", "command": "bash ${PLUGIN_ROOT}/scripts/check-completeness.sh", "timeout": 30 } ] } ] } ``` **Decision output:** ```json { "decision": "approve|block", "reason": "Explanation", "systemMessage": "Additional context" } ``` ### SubagentStop Execute when subagent considers stopping. Use to ensure subagent completed its task. Similar to Stop hook, but for subagents. Observe only — it cannot block or add context. ### UserPromptSubmit Execute when user submits a prompt. Use to add context, validate, or block prompts. Takes no matcher. **Example:** ```json { "UserPromptSubmit": [ { "hooks": [ { "type": "command", "command": "bash ${PLUGIN_ROOT}/scripts/check-prompt.sh", "timeout": 10 } ] } ] } ``` ### SessionStart Execute when DeepSeek Harness session begins. Use to load context and set environment. **Example:** ```json { "SessionStart": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "bash ${PLUGIN_ROOT}/scripts/load-context.sh" } ] } ] } ``` **Special capability:** Persist environment variables using `$DSH_ENV_FILE`: ```bash echo "export PROJECT_TYPE=nodejs" >> "$DSH_ENV_FILE" ``` See `examples/load-context.sh` for complete example. ### Events without a DeepSeek Harness counterpart The host bridge recognizes seven events: `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `Stop`, `SubagentStart`, `SubagentStop`. The source format also carries `SessionEnd`, `PreCompact`, `PostCompact` and `Notification`; those have no interception point here and are skipped with a diagnostic. Do not build on them. ## Hook Output Format ### Standard Output (All Hooks) ```json { "continue": true, "suppressOutput": false, "systemMessage": "Message for the agent" } ``` - `continue`: If false, halt processing (default true) - `suppressOutput`: Hide output from transcript (default false) - `systemMessage`: Message shown to the agent ### Exit Codes - `0` - Success (stdout shown in transcript) - `2` - Blocking error (stderr fed back to the agent) - Other - Non-blocking error ## Hook Input Format All hooks receive JSON via stdin with common fields: ```json { "session_id": "abc123", "transcript_path": "/path/to/transcript.txt", "cwd": "/current/working/dir", "permission_mode": "ask|allow", "hook_event_name": "PreToolUse" } ``` **Event-specific fields:** - **PreToolUse/PostToolUse:** `tool_name`, `tool_input`, `tool_result` - **UserPromptSubmit:** `user_prompt` - **Stop/SubagentStop:** `reason` Access fields in prompts using `$TOOL_INPUT`, `$TOOL_RESULT`, `$USER_PROMPT`, etc. ## Environment Variables Available in all command hooks: - `$DSH_PROJECT_DIR` - Project root path - `$PLUGIN_ROOT` - Plugin directory (use for portable paths) - `$DSH_ENV_FILE` - SessionStart only: persist env vars here - `$DSH_REMOTE` - Set if running in remote context **Always use ${PLUGIN_ROOT} in hook commands for portability:** ```json { "type": "command", "command": "bash ${PLUGIN_ROOT}/scripts/validate.sh" } ``` ## Plugin Hook Configuration In plugins, define hooks in `com.deepseek.harness/hooks/hooks.json`, wrapped in a top-level `"hooks"` object: ```json { "hooks": { "PreToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "bash ${PLUGIN_ROOT}/scripts/validate-write.sh", "timeout": 10 } ] } ], "SessionStart": [ { "hooks": [ { "type": "command", "command": "bash ${PLUGIN_ROOT}/scripts/load-context.sh", "timeout": 10 } ] } ] } } ``` Plugin hooks merge with user's hooks and run in parallel. ## Matchers ### Tool Name Matching **Exact match:** ```json "matcher": "Write" ``` **Multiple tools:** ```json "matcher": "Read|Write|Edit" ``` **Wildcard (all tools):** ```json "matcher": "*" ``` **Regex patterns:** ```json "matcher": "mcp__.*__delete.*" // All MCP delete tools ``` **Note:** Matchers are case-sensitive. ### Common Patterns ```json // All MCP tools "matcher": "mcp__.*" // Specific plugin's MCP tools "matcher": "mcp__plugin_asana_.*" // All file operations "matcher": "Read|Write|Edit" // Bash commands only "matcher": "Bash" ``` ## Security Best Practices ### Input Validation Always validate inputs in command hooks: ```bash #!/bin/bash set -euo pipefail input=$(cat) tool_name=$(echo "$input" | jq -r '.tool_name') # Validate tool name format if [[ ! "$tool_name" =~ ^[a-zA-Z0-9_]+$ ]]; then echo '{"decision": "deny", "reason": "Invalid tool name"}' >&2 exit 2 fi ``` ### Path Safety Check for path traversal and sensitive files: ```bash file_path=$(echo "$input" | jq -r '.tool_input.file_path') # Deny path traversal if [[ "$file_path" == *".."* ]]; then echo '{"decision": "deny", "reason": "Path traversal detected"}' >&2 exit 2 fi # Deny sensitive files if [[ "$file_path" == *".env"* ]]; then echo '{"decision": "deny", "reason": "Sensitive file"}' >&2 exit 2 fi ``` See `examples/validate-write.sh` and `examples/validate-bash.sh` for complete examples. ### Quote All Variables ```bash # GOOD: Quoted echo "$file_path" cd "$DSH_PROJECT_DIR" # BAD: Unquoted (injection risk) echo $file_path cd $DSH_PROJECT_DIR ``` ### Set Appropriate Timeouts ```json { "type": "command", "command": "bash script.sh", "timeout": 10 } ``` **Defaults:** 60s per hook when the hook sets no timeout ## Performance Considerations ### Parallel Execution All matching hooks run **in parallel**: ```json { "PreToolUse": [ { "matcher": "Write", "hooks": [ {"type": "command", "command": "check1.sh"}, {"type": "command", "command": "check2.sh"} ] } ] } ``` **Design implications:** - Hooks don't see each other's output - Non-deterministic ordering - Design for independence ### Optimization 1. Use command hooks for quick deterministic checks 2. Keep hook commands small — heavy reasoning belongs in the session, where the model can delegate 3. Cache validation results in temp files 4. Minimize I/O in hot paths ## Temporarily Active Hooks Create hooks that activate conditionally by checking for a flag file or configuration: **Pattern: Flag file activation** ```bash #!/bin/bash # Only active when flag file exists FLAG_FILE="$DSH_PROJECT_DIR/.enable-strict-validation" if [ ! -f "$FLAG_FILE" ]; then # Flag not present, skip validation exit 0 fi # Flag present, run validation input=$(cat) # ... validation logic ... ``` **Pattern: Configuration-based activation** ```bash #!/bin/bash # Check configuration for activation CONFIG_FILE="$DSH_PROJECT_DIR/.agents/plugin-config.json" if [ -f "$CONFIG_FILE" ]; then enabled=$(jq -r '.strictMode // false' "$CONFIG_FILE") if [ "$enabled" != "true" ]; then exit 0 # Not enabled, skip fi fi # Enabled, run hook logic input=$(cat) # ... hook logic ... ``` **Use cases:** - Enable strict validation only when needed - Temporary debugging hooks - Project-specific hook behavior - Feature flags for hooks **Best practice:** Document activation mechanism in plugin README so users know how to enable/disable temporary hooks. ## Hook Lifecycle and Limitations ### Hooks Load at Session Start **Important:** Hooks are loaded when DeepSeek Harness session starts. Changes to hook configuration require restarting DeepSeek Harness. **Cannot hot-swap hooks:** - Editing `com.deepseek.harness/hooks/hooks.json` won't affect current session - Adding new hook scripts won't be recognized - Changing hook commands won't update - Must restart DeepSeek Harness: exit and run `dsh` again **To test hook changes:** 1. Edit hook configuration or scripts 2. Exit DeepSeek Harness session 3. Restart: `dsh` 4. New hook configuration loads 5. Test hooks with `dsh --debug` ### Hook Validation at Startup Hooks are validated when DeepSeek Harness starts: - Invalid JSON in hooks.json causes loading failure - Missing scripts cause warnings - Syntax errors reported in debug mode Use `/hooks` command to review loaded hooks in current session. ## Debugging Hooks ### Enable Debug Mode ```bash dsh --debug ``` Look for hook registration, execution logs, input/output JSON, and timing information. ### Test Hook Scripts Test command hooks directly: ```bash echo '{"tool_name": "Write", "tool_input": {"file_path": "/test"}}' | \ bash ${PLUGIN_ROOT}/scripts/validate.sh echo "Exit code: $?" ``` ### Validate JSON Output Ensure hooks output valid JSON: ```bash output=$(./your-hook.sh < test-input.json) echo "$output" | jq . ``` ## Quick Reference ### Hook Events Summary | Event | When | Use For | |-------|------|---------| | PreToolUse | Before tool | Validation, modification | | PostToolUse | After tool | Feedback, logging | | UserPromptSubmit | User input | Context, validation | | Stop | Agent stopping | Completeness check | | SubagentStart | Subagent starts | Context injection | | SubagentStop | Subagent done | Task validation | | SessionStart | Session begins | Context loading | `SessionEnd`, `PreCompact`, `PostCompact` and `Notification` have no counterpart in the host bridge and are skipped with a diagnostic. ### Best Practices **DO:** - ✅ Keep hook commands deterministic and fast - ✅ Use ${PLUGIN_ROOT} for portability - ✅ Validate all inputs in command hooks - ✅ Quote all bash variables - ✅ Set appropriate timeouts - ✅ Return structured JSON output - ✅ Test hooks thoroughly **DON'T:** - ❌ Use hardcoded paths - ❌ Trust user input without validation - ❌ Create long-running hooks - ❌ Rely on hook execution order - ❌ Modify global state unpredictably - ❌ Log sensitive information ## Additional Resources ### Reference Files For detailed patterns and advanced techniques, consult: - **`references/patterns.md`** - Common hook patterns (8+ proven patterns) - **`references/migration.md`** - Migrating from basic to advanced hooks - **`references/advanced.md`** - Advanced use cases and techniques ### Example Hook Scripts Working examples in `examples/`: - **`validate-write.sh`** - File write validation example - **`validate-bash.sh`** - Bash command validation example - **`load-context.sh`** - SessionStart context loading example ### Utility Scripts Development tools in `scripts/`: - **`validate-hook-schema.sh`** - Validate hooks.json structure and syntax - **`test-hook.sh`** - Test hooks with sample input before deployment - **`hook-linter.sh`** - Check hook scripts for common issues and best practices ### External Resources - **Official Docs**: the harness hooks documentation - **Examples**: See security-guidance plugin in marketplace - **Testing**: Use `dsh --debug` for detailed logs - **Validation**: Use `jq` to validate hook JSON output ## Implementation Workflow To implement hooks in a plugin: 1. Identify events to hook into (PreToolUse, Stop, SessionStart, etc.) 2. Decide which event the check belongs at 3. Write hook configuration in `com.deepseek.harness/hooks/hooks.json` 4. For command hooks, create hook scripts 5. Use ${PLUGIN_ROOT} for all file references 6. Validate configuration with `scripts/validate-hook-schema.sh com.deepseek.harness/hooks/hooks.json` 7. Test hooks with `scripts/test-hook.sh` before deployment 8. Test in DeepSeek Harness with `dsh --debug` 9. Document hooks in plugin README Command hooks are the supported type here. Keep them deterministic, fast, and free of side effects.