PreToolUse / PostToolUse / PostToolUseFailure), sessions and subagents (SessionStart / SessionEnd / Stop / SubagentStart / SubagentStop / StopFailure), user interaction (UserPromptSubmit / Notification / PermissionRequest / PermissionDenied / Elicitation / ElicitationResult), context (PreCompact / PostCompact / InstructionsLoaded / ConfigChange), tasks and teams (TaskCreated / TaskCompleted / TeammateIdle), files and environment (FileChanged / CwdChanged / WorktreeCreate / WorktreeRemove), and startup/maintenance (Setup). For the complete event list, see the event table in the plugin reference.session_id, session transcript files, and the current working directory./hooks panel. All external modifications take effect only after review in the panel, ensuring security.Scope | File path | Description |
User level | ~/.codebuddy/settings.json | Applicable to all projects |
Project level | <project root>/.codebuddy/settings.json | Configuration shared with project members |
Project-local | <project root>/.codebuddy/settings.local.json | Local uncommitted configuration |
Enterprise policy | Policy file released through integration | Centrally managed by the enterprise |
{"hooks": {"EventName": [{"matcher": "ToolPattern","hooks": [{"type": "command","command": "your-command-here"}]}]}}
Write matches any tool name containing "Write" (such as Write and NotebookWrite).^Write$ to match only the Write tool.Edit|Write or Web.** character.""."command" for shell commands, or "prompt" for LLM-based evaluation."command") The shell command to execute. You can use the $CODEBUDDY_PROJECT_DIR environment variable. The command runs in the user's default shell ($SHELL) on macOS/Linux, and is forced to run in Git Bash on Windows (cmd.exe or PowerShell are not supported), so it must be compatible with bash syntax."prompt") The prompt sent to the LLM for evaluation (only supported for Stop, UserPromptSubmit, and PreToolUse events).{"hooks": {"UserPromptSubmit": [{"hooks": [{"type": "command","command": "python3 /path/to/prompt-validator.py"}]}]}}
CODEBUDDY_PROJECT_DIR environment variable (available only when CodeBuddy Code generates hook commands) to reference scripts stored in your project:{"hooks": {"PostToolUse": [{"matcher": "Write|Edit","hooks": [{"type": "command","command": "\\"$CODEBUDDY_PROJECT_DIR\\"/.codebuddy/hooks/check-style.sh"}]}]}}
python3 instead of executing the .py file directly. This is because Git Bash may not correctly recognize the shebang line (#!/usr/bin/env python3) on Windows, even if the script contains it:"command": "python3 \\"$CODEBUDDY_PROJECT_DIR\\"/.codebuddy/hooks/my_hook.py"
hooks/hooks.json file, or in a file at a custom path provided by the hooks field.${CODEBUDDY_PLUGIN_ROOT} environment variable to reference plugin files.{"description": "Automatic code formatting","hooks": {"PostToolUse": [{"matcher": "Write|Edit","hooks": [{"type": "command","command": "${CODEBUDDY_PLUGIN_ROOT}/scripts/format.sh","timeout": 30}]}]}}
type: "command"), CodeBuddy Code also supports prompt-based hooks (type: "prompt"), which use an LLM to evaluate whether to allow or block an operation.Stop, UserPromptSubmit, and PreToolUse events are supported./goal <condition> to keep CodeBuddy working until the condition is met, without writing hook configuration manually. If your decision logic can be expressed as conditional text, use /goal first. Only fall back to writing your own prompt hook in this section when you need more complex prompt orchestration or collaboration across multiple events.lite slot, mapped separately by model provider).Event | Purpose |
Stop | Intelligently decide whether CodeBuddy should continue working |
UserPromptSubmit | Use LLM to assist in verifying user prompts. |
PreToolUse | Make context-aware permission decisions. |
Feature | Command Hooks | Prompt Hooks |
Execution Method | Run bash scripts. | Query the LLM. |
Decision Logic | Implement in code. | LLM evaluates the context. |
Setup Complexity | Requires a script file. | Only prompt configuration required. |
Context Awareness | Limited by script logic. | Natural language understanding |
Performance | Fast (local execution) | Slower (API call) |
Use Cases | Deterministic rules | Context-aware decision-making |
{"hooks": {"Stop": [{"hooks": [{"type": "prompt","prompt": "Evaluate if CodeBuddy should stop: $ARGUMENTS. Check if all tasks are complete."}]}]}}
"prompt"$ARGUMENTS as a placeholder for the hook input JSON. It will be replaced directly.$ARGUMENTS does not exist, the input JSON is appended to the end of the prompt in the \\n\\nARGUMENTS:\\n{JSON} format.ok: false. When set to true, the behavior is similar to /goal: the reason is injected into the conversation history, and the Agent keeps looping until the condition is met. This is only meaningful for Stop/SubagentStop events. The default is false (the Agent stops).{"ok": true | false,"reason": "Explanation for the decision", // Required when ok is false"impossible": false // Optional, effective only for Stop events}
ok: true allows the operation, and false blocks it.reason: Required when ok is false. The explanation displayed to CodeBuddy.impossible: An optional boolean that is only meaningful for the Stop hook. {ok: false, impossible: true} indicates that the evaluator has determined that the goal is impossible to complete in the current session (the conditions are contradictory, required resources are unavailable, or the model has exhausted all reasonable attempts). CodeBuddy stops looping, and the UI displays the "unachievable" final state. A regular {ok: false} is still treated as "not achieved, continue working".Stop hook returns {ok: false}, the reason text is not simply "displayed to CodeBuddy". Instead, it is injected into the conversation history as an internal user message with isMeta=true, allowing the main model to see the evaluator's perspective in the next round and precisely complete the missing parts. This is the core mechanism by which the prompt-based Stop hook drives multi-round iterative convergence (the /goal command relies on this chain under the hood).{"hooks": {"Stop": [{"hooks": [{"type": "prompt","prompt": "You are evaluating whether CodeBuddy should stop working. Context: $ARGUMENTS\\n\\nAnalyze the conversation and determine if:\\n1. All user-requested tasks are complete\\n2. Any errors need to be addressed\\n3. Follow-up work is needed\\n\\nRespond with JSON: {\\"ok\\": true} to allow stopping, or {\\"ok\\": false, \\"reason\\": \\"your explanation\\"} to continue working.","timeout": 30}]}]}}
continueOnBlock: true, the prompt Stop hook can drive the Agent to continue working when the condition is not met, similar to the effect of /goal:{"hooks": {"Stop": [{"hooks": [{"type": "prompt","prompt": "Check if all tests pass and code is properly formatted. Context: $ARGUMENTS\\n\\nIf tests pass and code is clean, return {\\"ok\\": true}.\\nIf not, return {\\"ok\\": false, \\"reason\\": \\"describe what still needs to be fixed\\"}.","continueOnBlock": true,"timeout": 30}]}]}}
continueOnBlock is true:ok: true → The Agent stops normally.ok: false → reason is injected into the conversation history, and the Agent continues working until the condition is met.ok: false, impossible: true → The Agent stops and displays "Goal unachievable".continueOnBlock is false (default):ok: false → The Agent stops and does not continue looping.{"hooks": {"UserPromptSubmit": [{"hooks": [{"type": "prompt","prompt": "Evaluate if this user prompt is safe and appropriate. Input: $ARGUMENTS\\n\\nCheck if:\\n- The prompt contains sensitive information (passwords, secrets)\\n- The request is clear and actionable\\n- Any security concerns exist\\n\\nReturn: {\\"ok\\": true} to allow, or {\\"ok\\": false, \\"reason\\": \\"explanation\\"} to block."}]}]}}
{"hooks": {"PreToolUse": [{"matcher": "Bash","hooks": [{"type": "prompt","prompt": "Evaluate if this bash command should be allowed. Input: $ARGUMENTS\\n\\nCheck if:\\n- The command is safe and non-destructive\\n- It doesn't access sensitive files or directories\\n- It aligns with the user's stated goals\\n\\nReturn: {\\"ok\\": true} to allow, or {\\"ok\\": false, \\"reason\\": \\"explanation\\"} to deny."}]}]}}
Event | Trigger Timing | matcher Field | Typical Scenario |
PreToolUse | Before tool execution | Supported (tool name) | Validate commands, perform secondary approval, and record logs. |
PostToolUse | After the tool is successfully executed | Supported | Automatically format, supplement context, and compress/replace tool results. |
Notification | Permission request or no-input reminder for 60 seconds | Partially supported | Desktop notifications and IM notifications |
UserPromptSubmit | When a user submits a message<br/>Note: Internal commands are not included | Not supported | Content review and context injection |
Stop | When the main agent response ends | Not supported | Request to continue execution and append reminders |
SubagentStop | When a subagent (TaskTool) ends | Not supported | Continue sub-task execution or provide supplementary instructions |
PreCompact | Before context compression | Supported ( manual/auto) | Preserve key information and prevent compression |
SessionStart | When a session is created or resumed | Supported ( startup/resume/clear/compact) | Environment initialization and variable injection |
SessionEnd | When a session ends | Supported ( clear/logout/prompt_input_exit/other) | Clean up resources and persist logs |
Task - Subagent taskBash - Shell commandGlob - File pattern matchingGrep - Content searchRead - File readEdit - File editWrite - File writeWebFetch, WebSearch - Web operationspermission_prompt - Permission request from CodeBuddy Codeidle_prompt - When CodeBuddy is waiting for user input (after being idle for more than 60 seconds)auth_success - Authentication success notificationelicitation_dialog - When CodeBuddy Code requires input guided by an MCP tool (not yet supported){"hooks": {"Notification": [{"matcher": "permission_prompt","hooks": [{"type": "command","command": "/path/to/permission-alert.sh"}]},{"matcher": "idle_prompt","hooks": [{"type": "command","command": "/path/to/idle-notification.sh"}]}]}}
/goal <condition> registers a Stop hook that keeps CodeBuddy working until the condition is met, and automatically handles details such as three-state evaluation (met / not met, continue / cannot be met), turn counting, token statistics, and automatic recovery with /resume. When you need session-level "keep working until X", prefer /goal instead of writing hook configuration manually.manual - Invoked from /compactauto - Invoked from automatic compaction (because the context window is full)startup - Invoked from startupresume - Invoked from --resume, --continue, or /resumeclear - Invoked from /clearcompact - Invoked from automatic or manual compactionclear - Clears the session using the /clear commandlogout - Logs out the userprompt_input_exit - Exits when the prompt input is visible.other - Other exit reasons (including normal exit){// Common fields"session_id": "string","transcript_path": "string", // Path to the conversation JSON"cwd": "string", // Current working directory when the hook is invoked"permission_mode": "string", // Current permission mode: "default", "plan", "acceptEdits", or "bypassPermissions"// Event-specific fields"hook_event_name": "string"// ...}
{"session_id": "abc123","transcript_path": "/Users/.../.codebuddy/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl","cwd": "/Users/...","permission_mode": "default","hook_event_name": "PreToolUse","tool_name": "Write","tool_input": {"file_path": "/path/to/file.txt","content": "file content"}}
{"session_id": "abc123","transcript_path": "/Users/.../.codebuddy/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl","cwd": "/Users/...","permission_mode": "default","hook_event_name": "PostToolUse","tool_name": "Write","tool_input": {"file_path": "/path/to/file.txt","content": "file content"},"tool_response": {"filePath": "/path/to/file.txt","success": true}}
{"session_id": "abc123","transcript_path": "/Users/.../.codebuddy/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl","cwd": "/Users/...","permission_mode": "default","hook_event_name": "Notification","message": "CodeBuddy needs your permission to use Bash","notification_type": "permission_prompt"}
{"session_id": "abc123","transcript_path": "/Users/.../.codebuddy/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl","cwd": "/Users/...","permission_mode": "default","hook_event_name": "UserPromptSubmit","prompt": "Write a function to calculate the factorial of a number"}
stop_hook_active is true when CodeBuddy Code has already continued as a result of a stop hook.{"session_id": "abc123","transcript_path": "/Users/xxx/.codebuddy/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl","permission_mode": "default","hook_event_name": "Stop","stop_hook_active": true}
custom_instructions comes from the content that the user passes to /compact. For automatic triggering, custom_instructions is empty.{"session_id": "abc123","transcript_path": "/Users/xxx/.codebuddy/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl","permission_mode": "default","hook_event_name": "PreCompact","trigger": "manual","custom_instructions": ""}
{"session_id": "abc123","transcript_path": "/Users/xxx/.codebuddy/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl","permission_mode": "default","hook_event_name": "SessionStart","source": "startup"}
{"session_id": "abc123","transcript_path": "/Users/xxx/.codebuddy/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl","cwd": "/Users/...","permission_mode": "default","hook_event_name": "SessionEnd","reason": "other"}
reason/stopReason field in JSON or plain text) > stderr. That is, stderr is only a fallback and is passed to CodeBuddy only when stdout produces no output. Therefore, debug logs can be safely written to stderr without polluting the feedback message to the Agent.Hook Event | Action |
PreToolUse | Block tool invocation and display a message to CodeBuddy. |
PostToolUse | Display a message to CodeBuddy (the tool has run, providing additional context); the tool result can be replaced with updatedToolOutput. |
Notification | N/A. Only display a message to the user. |
UserPromptSubmit | Block prompt processing, clear the prompt, and display a message only to the user. |
Stop | Block the stop, display a message to CodeBuddy, and continue the conversation. |
SubagentStop | Block the stop, display a message to the CodeBuddy subagent, and continue execution. |
PreCompact | Block the compaction operation and display a message only to the user. |
SessionStart | N/A. Only display a message to the user. |
SessionEnd | N/A. Only display a message to the user. |
{"continue": true, // Whether CodeBuddy continues after the hook executes (default: true)"stopReason": "string", // The message displayed to CodeBuddy when continue is false"reason": "string", // An alias for stopReason, and the two are equivalent."suppressOutput": true, // Hides stdout in transcript mode (default: false)"systemMessage": "string" // An optional warning message displayed to the user (not passed to the Agent)}
stopReason / reason: The message passed to the CodeBuddy Agent to explain why the operation was blocked.systemMessage: A warning message displayed only to the user and not passed to the Agent.{"hookSpecificOutput": {"hookEventName": "PreToolUse","permissionDecision": "allow" | "deny" | "ask","permissionDecisionReason": "The reason description displayed in the permission dialog","modifiedInput": {"field_to_modify": "new value"}}}
"allow" bypasses the permission system and executes the tool directly."deny" blocks the tool call execution, and permissionDecisionReason is passed to the Agent."ask" prompts the user to confirm the tool call in the UI, and permissionDecisionReason is displayed in the confirmation dialog.modifiedInput allows you to modify the input parameters of the tool before execution (partial field override).additionalContext);updatedToolOutput).{"hookSpecificOutput": {"hookEventName": "PostToolUse","additionalContext": "Additional information provided to CodeBuddy, such as code compliance check results."}}
{"hookSpecificOutput": {"hookEventName": "PostToolUse","updatedToolOutput": "The streamlined tool output."}}
updatedToolOutput applies to all tools (both built-in tools and MCP tools).additionalContext: additionalContext appends (the result only becomes longer), while updatedToolOutput replaces (the result can become shorter).updatedToolOutput, and then append additionalContext to the replaced content.updatedToolOutput is an array, it is used directly as the MCP content array. Otherwise, it is wrapped as a single text block.decision: "block" field is deprecated. Since the tool has already finished executing, the operation cannot actually be "blocked" at this point.{"continue": false, // Set to false to block prompt processing."reason": "The reason for blocking (displayed to the user only)","hookSpecificOutput": {"hookEventName": "UserPromptSubmit","additionalContext": "Additional context injected into CodeBuddy."}}
decision: "block" field is deprecated. Use continue: false instead.{"continue": false, // Set to false to prevent stopping and allow the Agent to continue working."reason": "The reason why the Agent needs to continue working."}
decision: "block" field is deprecated. Use continue: false instead.{"hookSpecificOutput": {"hookEventName": "SessionStart","additionalContext": "My additional context here"}}
mcp__<server>__<tool> pattern, for example:mcp__memory__create_entities - The entity creation tool of the Memory server.mcp__filesystem__read_file - The file reading tool of the Filesystem server.mcp__github__search_repositories - The search tool of the GitHub server.{"hooks": {"PreToolUse": [{"matcher": "mcp__memory__.*","hooks": [{"type": "command","command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"}]},{"matcher": "mcp__.*__write.*","hooks": [{"type": "command","command": "python3 /home/user/scripts/validate-mcp-write.py"}]}]}}
"$VAR" instead of $VAR.. in file paths."$CODEBUDDY_PROJECT_DIR" for project paths)..env, .git/, keys, and similar items./hooks menu before they can be applied.$SHELL environment variable, typically bash or zsh), falling back to /bin/sh.CODEBUDDY_CODE_GIT_BASH_PATH environment variable.CODEBUDDY_CODE_SHELL environment variable (only POSIX shells are supported: bash, zsh, sh).CODEBUDDY_PROJECT_DIR environment variable contains the absolute path of the project root directory.--debug)./hooks to see whether your hook is registered.codebuddy --debug to view hook execution details.\\" in JSON strings.codebuddy --debug to view detailed hook execution.codebuddy --debug to view hook execution details:[DEBUG] Executing hooks for PostToolUse:Write[DEBUG] Getting matching hook commands for PostToolUse with query: Write[DEBUG] Found 1 hook matchers in settings[DEBUG] Matched 1 hooks for query "Write"[DEBUG] Found 1 hook commands to execute[DEBUG] Executing hook command: <Your command> with timeout 60000ms[DEBUG] Hook command completed with status 0: <Your stdout>
~/.codebuddy/settings.json, you can also declare the hooks field directly in the YAML frontmatter of a custom Agent's .md file or a Skill's SKILL.md. This approach distributes Hooks together with Agents / Skills as an "atomic unit", and the scope automatically opens and closes with the subagent lifecycle without polluting the main session.hooks field is exactly the same as in settings.json—grouped by event name, with several {matcher?, hooks[]} configurations under each event. The hook type supports four types: command / prompt / agent / http:---name: my-reviewerdescription: Code reviewer with pre-tool-use guardhooks:PreToolUse:- matcher: Bashhooks:- type: commandcommand: ./guard.shonce: trueSubagentStop:- hooks:- type: commandcommand: echo "reviewer finished"- type: httpurl: https://example.com/notifymethod: POST---
context: fork (the injection path has no clear lifecycle boundary and is not integrated). Custom Agents always support them.ScopedHookRegistry, and when the subagent exits, they are automatically unregistered. Hooks take effect only for that subagent's own tool calls and lifecycle events.Stop → SubagentStop rewriting: Writing a Stop event in the frontmatter is automatically rewritten to SubagentStop—when a subagent completes, it does not trigger the main session's Stop. Writing Stop is intended to express the semantics of "the subagent ending itself."settings.json and the plugin's hooks/hooks.json, and all are triggered in parallel.allowUntrustedFrontmatterHooks)Source | Registered by Default or Not |
Built-in Product agents/skills | ✔ Allowed automatically |
.codebuddy/agents/*.md (user/project-local agents) | ✖ Rejected by default |
.codebuddy/skills/SKILL.md (user/project-local skills) | ✖ Rejected by default |
Agents/skills distributed through the plugin marketplace | ✖ Rejected by default |
Plugin hooks/hooks.json (not frontmatter) | ✔ Not restricted by the gate |
~/.codebuddy/settings.json:{"allowUntrustedFrontmatterHooks": true}
[AgentTask] Frontmatter hooks from skill 'xxx' skipped(source not admin-trusted; enable `allowUntrustedFrontmatterHooks` in settings to allow)
event 'YYY' invalid: <detailed reason> for easier troubleshooting.unknown event 'XXX') and does not invalidate the entire frontmatter.Malformed YAML frontmatter in '<path>'.CODEBUDDY_DEBUG=1, you can see registration logs such as [ScopedHookRegistry] registered N hook config(s) for scope '<sessionId>' (...) to confirm whether hooks are in place.Was 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