Welcome to Snow CLI! Agentic coding in your terminal.
Hooks are a powerful extension mechanism provided by Snow CLI that allow you to automatically execute custom commands or trigger interactive prompts at key points in the AI workflow. With Hooks, you can:
- Automatically execute scripts or commands at specific moments
- Implement workflow automation
- Integrate external tools and services
- Perform validation or logging before and after critical operations
- Trigger interactive prompts at the end of workflows
graph TB
Start([AI Workflow Start]) --> UserMsg{User Sends Message}
UserMsg -->|Trigger| Hook1[onUserMessage Hook]
Hook1 --> CheckMatch1{Match Rule?}
CheckMatch1 -->|Yes| Execute1[Execute Hook Actions]
CheckMatch1 -->|No| Continue1[Continue Flow]
Execute1 --> Continue1
Continue1 --> AIProcess[AI Process Message]
AIProcess --> ToolCall{AI Call Tool?}
ToolCall -->|Yes| Hook2[beforeToolCall Hook]
Hook2 --> CheckMatch2{Match Tool Name?}
CheckMatch2 -->|Yes| Execute2[Execute Hook Actions]
CheckMatch2 -->|No| Continue2[Continue Call]
Execute2 --> Continue2
Continue2 --> NeedConfirm{Need User Confirmation?}
NeedConfirm -->|Yes| Hook3[toolConfirmation Hook]
Hook3 --> CheckMatch3{Match Tool Name?}
CheckMatch3 -->|Yes| Execute3[Execute Hook Actions]
CheckMatch3 -->|No| UserConfirm[User Confirm]
Execute3 --> UserConfirm
UserConfirm --> ToolExec[Execute Tool]
NeedConfirm -->|No| ToolExec
ToolExec --> Hook4[afterToolCall Hook]
Hook4 --> CheckMatch4{Match Tool Name?}
CheckMatch4 -->|Yes| Execute4[Execute Hook Actions]
CheckMatch4 -->|No| Continue4[Continue Flow]
Execute4 --> Continue4
Continue4 --> MoreTools{More Tools?}
MoreTools -->|Yes| ToolCall
MoreTools -->|No| AIResponse[AI Generate Response]
ToolCall -->|No| AIResponse
AIResponse --> SubAgent{Call Sub-Agent?}
SubAgent -->|Yes| SubProcess[Sub-Agent Process]
SubProcess --> Hook5[onSubAgentComplete Hook]
Hook5 --> CheckMatch5{Match Rule?}
CheckMatch5 -->|Yes| Execute5[Execute Hook Actions<br/>May be Prompt]
CheckMatch5 -->|No| Continue5[Continue Flow]
Execute5 --> Continue5
Continue5 --> CheckCompress
SubAgent -->|No| CheckCompress{Need Compress Context?}
CheckCompress -->|Yes| Hook6[beforeCompress Hook]
Hook6 --> Execute6[Execute Hook Actions]
Execute6 --> Compress[Execute Compression]
Compress --> End
CheckCompress -->|No| End([Flow End])
End --> Hook7[onStop Hook]
Hook7 --> Execute7[Execute Hook Actions<br/>May be Prompt]
Execute7 --> FinalEnd([Final End])
style Hook1 fill:#ffe1e1
style Hook2 fill:#e1f5ff
style Hook3 fill:#fff4e1
style Hook4 fill:#e1ffe1
style Hook5 fill:#ffe1f5
style Hook6 fill:#f5e1ff
style Hook7 fill:#ffe1e1
style Execute1 fill:#ffcccc
style Execute2 fill:#ccecff
style Execute3 fill:#fff0cc
style Execute4 fill:#ccffcc
style Execute5 fill:#ffccf5
style Execute6 fill:#f0ccff
style Execute7 fill:#ffcccc
Snow CLI provides 8 hook types, each triggered at different moments:
Trigger Time: When starting a new session or resuming an existing session
Use Cases:
- Initialize working environment
- Check dependencies and configurations
- Load project-specific settings
- Log session start time
Example:
{
"onSessionStart": [
{
"description": "Check development environment",
"hooks": [
{
"type": "command",
"command": "node --version && npm --version",
"timeout": 5000,
"enabled": true
}
]
}
]
}Trigger Time: When user sends a message
Context Parameters:
{
"message": "User message content", // User message text
"imageCount": 2, // Number of images attached
"source": "cli" // Message source: "cli" or "mcp"
}Use Cases:
- Log user requests
- Preprocess user input
- Trigger specific monitoring or statistics
- Execute automated tasks based on message content
Accessing Context:
For command type hooks, context is passed via stdin as JSON. You can read it using:
const context = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
console.log('User message:', context.message);
console.log('Image count:', context.imageCount);Example:
{
"onUserMessage": [
{
"description": "Log user messages",
"hooks": [
{
"type": "command",
"command": "echo \"$(date): User message logged\" >> .snow/logs/user-messages.log",
"timeout": 3000,
"enabled": true
}
]
}
]
}Trigger Time: Before AI calls a tool (supports tool matching)
Special Feature: Supports matcher field to match specific tool names
Context Parameters:
{
"toolName": "filesystem-edit", // Tool name to be called
"args": {
// Tool arguments
"filePath": "src/index.ts",
"startLine": 10,
"endLine": 20,
"newContent": "..."
}
}Use Cases:
- Backup before file operations
- Environment check before executing commands
- Log tool call history
- Preprocessing for specific tools
Placeholder Usage:
For prompt type hooks, you can use the $TOOLSRESULT$ placeholder to access the full context data.
Matcher Syntax:
- Exact match:
filesystem-read - Wildcard match:
filesystem-*(matches all filesystem tools) - Multiple tools:
filesystem-read,filesystem-edit(comma-separated)
Example:
{
"beforeToolCall": [
{
"matcher": "filesystem-edit,filesystem-create",
"description": "Auto backup before file changes",
"hooks": [
{
"type": "command",
"command": "git add . && git commit -m \"Auto backup before file changes\"",
"timeout": 10000,
"enabled": true
}
]
}
]
}Trigger Time: During tool confirmation (including sensitive command checks)
Special Feature: Supports matcher field to match specific tool names
Use Cases:
- Execute additional checks before user confirms sensitive operations
- Log operations requiring confirmation
- Send notifications to team members
- Pre-confirmation processing for specific tools
Example:
{
"toolConfirmation": [
{
"matcher": "terminal-execute",
"description": "Send notification on sensitive command confirmation",
"hooks": [
{
"type": "command",
"command": "curl -X POST https://hooks.slack.com/... -d '{\"text\":\"Sensitive command needs confirmation\"}'",
"timeout": 5000,
"enabled": true
}
]
}
]
}Trigger Time: After tool call completes (supports tool matching)
Special Feature: Supports matcher field to match specific tool names
Context Parameters:
{
"toolName": "filesystem-edit", // Tool name
"args": {
// Tool arguments
"filePath": "src/index.ts",
"startLine": 10,
"endLine": 20,
"newContent": "..."
},
"result": {
// Tool execution result
"success": true,
"message": "File edited successfully"
},
"error": null // Error message (if execution failed)
}Use Cases:
- Run tests after file modifications
- Run code formatting after code changes
- Log tool execution results
- Post-processing for specific tools
Placeholder Usage:
For prompt type hooks, you can use the $TOOLSRESULT$ placeholder to access the full context data (including result and error).
Example:
{
"afterToolCall": [
{
"matcher": "filesystem-edit",
"description": "Auto format after code changes",
"hooks": [
{
"type": "command",
"command": "npm run format",
"timeout": 30000,
"enabled": true
}
]
}
]
}Trigger Time: When sub-agent task completes
Special Feature: Supports prompt type Action (interactive prompt)
Context Parameters:
{
"agentId": "agent_explore", // Sub-agent ID
"agentName": "Explore Agent", // Sub-agent name
"content": "Sub-agent response...", // Sub-agent output content
"success": true, // Whether execution succeeded
"usage": {
// Token usage statistics
"totalTokens": 1500,
"promptTokens": 1000,
"completionTokens": 500
}
}Use Cases:
- Collect user feedback after sub-agent completes
- Ask user whether to continue to next step
- Let user choose handling method
- Log sub-agent execution results
Placeholder Usage:
For prompt type hooks, you can use the $SUBAGENTRESULT$ placeholder to access sub-agent context data.
Prompt Type Description:
prompttype pauses AI flow and waits for user input- User input is sent as a new message to AI
- Can only be used in
onSubAgentCompleteandonStop - If a rule has
prompttype, no other Actions can be added
Example (Prompt Type):
{
"onSubAgentComplete": [
{
"description": "Ask user after sub-agent completes",
"hooks": [
{
"type": "prompt",
"prompt": "Sub-agent has completed the task. Do you need to continue? Please enter your instructions:",
"timeout": 30000,
"enabled": true
}
]
}
]
}Example (Command Type):
{
"onSubAgentComplete": [
{
"description": "Log sub-agent results",
"hooks": [
{
"type": "command",
"command": "echo \"Sub-agent completed at $(date)\" >> .snow/logs/subagent.log",
"timeout": 3000,
"enabled": true
}
]
}
]
}Trigger Time: Before running context compression operation
Use Cases:
- Save context snapshot before compression
- Log compression operation timestamp
- Trigger context backup
- Send compression notification
Example:
{
"beforeCompress": [
{
"description": "Save context before compression",
"hooks": [
{
"type": "command",
"command": "echo \"Context compression at $(date)\" >> .snow/logs/compression.log",
"timeout": 3000,
"enabled": true
}
]
}
]
}Trigger Time: When user stops AI flow (Ctrl+C or end session)
Special Feature: Supports prompt type Action (interactive prompt)
Context Parameters:
{
"messages": [
// Complete session message history
{
"role": "user",
"content": "User message content"
},
{
"role": "assistant",
"content": "AI response content"
}
// ... more messages
]
}Use Cases:
- Ask user whether to save work before stopping
- Collect user feedback
- Execute cleanup operations
- Log stop reason
Placeholder Usage:
For prompt type hooks, you can use the $STOPSESSION$ placeholder to access session context data.
Example (Prompt Type):
{
"onStop": [
{
"description": "Ask before stopping",
"hooks": [
{
"type": "prompt",
"prompt": "About to stop AI. Do you need to save current work? Please enter instructions:",
"timeout": 30000,
"enabled": true
}
]
}
]
}- Launch Snow CLI
- Select "Hooks Configuration" option in main menu
- Choose configuration scope (Global or Project)
graph LR
Config[Hooks Configuration] --> Global[Global Scope]
Config --> Project[Project Scope]
Global --> GlobalPath[~/.snow/hooks/]
Project --> ProjectPath[./.snow/hooks/]
GlobalPath --> AllProjects[Apply to All Projects]
ProjectPath --> CurrentProject[Only Apply to Current Project]
style Global fill:#e1f5ff
style Project fill:#e1ffe1
style GlobalPath fill:#ccecff
style ProjectPath fill:#ccffcc
Global Hooks:
- Storage location:
~/.snow/hooks/ - Scope: All projects using Snow CLI
- Use cases: Common workflows, global monitoring, unified logging
Project Hooks:
- Storage location:
./.snow/hooks/(current project directory) - Scope: Current project only
- Use cases: Project-specific automation, special build processes, project-level validation
Execution Priority: Both project and global hooks will execute, with project hooks executing first
The configuration interface displays all 8 hook types:
- Configured hooks show
[✓]marker - Unconfigured hooks show
[ ]marker - Display the number of rules for each hook
- Bottom shows description of currently selected hook
Use ↑/↓ arrow keys to select the hook type to configure, press Enter to enter details page
Displays all rules under this hook:
- Rule list (shows description, number of Actions, Matcher information)
- Add new rule option
- Delete entire hook configuration option
- Return to previous level option
Select a rule or choose "Add New Rule" to enter editing interface:
Basic Fields:
-
Description (required)
- Brief description of the rule
- Press Enter or Tab to move to next field
- Helps you quickly identify rule purpose
-
Matcher (only for tool hooks)
- Only shown in
beforeToolCall,toolConfirmation,afterToolCall - Used to match specific tool names
- Supports wildcards:
filesystem-* - Supports multiple tools:
filesystem-read,filesystem-edit - Leave empty to match all tools
- Only shown in
Action Management:
Each rule can contain multiple Actions, executed in order:
- View existing Action list
- Add new Action
- Edit existing Actions
- Delete Actions
Select an Action or choose "Add Action" to enter Action editing interface:
Action Fields:
-
Enabled Status (required)
- Use Space key to toggle enabled/disabled
[✓]means enabled,[ ]means disabled- Disabled Actions won't execute but configuration is retained
-
Type (required)
command: Execute commandprompt: Interactive prompt (only supported inonSubAgentCompleteandonStop)- Press Space key to toggle type
- Type switching has restrictions (see below)
-
Command (when type=command)
- Command line command to execute
- Supports pipes and complex commands
- Example:
npm run build && npm test
-
Prompt (when type=prompt)
- Prompt text to display to user
- User input will be sent as a new message to AI
- Example: "Please enter your next instruction:"
-
Timeout (optional)
- Timeout duration (milliseconds)
- Default: command=5000ms, prompt=30000ms
- Action will be terminated after timeout
graph TB
Start([Select Hook Type]) --> CheckHook{Hook Type}
CheckHook -->|onSubAgentComplete<br/>or onStop| CanPrompt[Can use Prompt or Command]
CheckHook -->|Other Hook Types| OnlyCommand[Can only use Command]
CanPrompt --> CheckExist{Are there existing<br/>Actions in rule?}
CheckExist -->|No Actions| ChooseType1[Can choose any type]
CheckExist -->|Has Prompt| NoMore1[Cannot add more Actions]
CheckExist -->|Has Command| OnlyCommand2[Can only add Command]
ChooseType1 --> SelectPrompt{Select Prompt?}
SelectPrompt -->|Yes| SinglePrompt[Can only have this one Prompt<br/>Cannot add other Actions]
SelectPrompt -->|No| MultiCommand[Can add multiple Commands]
style CanPrompt fill:#e1ffe1
style OnlyCommand fill:#ffe1e1
style OnlyCommand2 fill:#ffe1e1
style NoMore1 fill:#ffcccc
style SinglePrompt fill:#fff0cc
style MultiCommand fill:#ccffcc
Restriction Rules:
-
Prompt Type Restrictions:
- Can only be used in
onSubAgentCompleteandonStop - If a rule has Prompt, it cannot have any other Actions
- Prompt must exist alone
- Can only be used in
-
Command Type:
- Can be used in all hook types
- A rule can have multiple Command Actions
- If the rule already has Prompt, cannot add Command
-
Type Switching:
- System automatically validates when switching types
- Non-compliant switches will be blocked
Save Rule:
- Select "Save Rule" in rule editing interface
- Configuration is immediately saved to corresponding scope
- Automatically returns to Hook details page after saving
Delete Rule:
- Select "Delete Rule" in rule editing interface or press
Dkey - Press
Dkey for quick delete (must be in rule editing interface) - Automatically returns to Hook details page after deletion
Delete Hook Configuration:
- Select "Delete Hook" in Hook details page
- Will delete the configuration file for this Hook
- Returns to Hook list after deletion
- ↑/↓: Navigate between Hook types
- Enter: Enter selected Hook details
- ESC: Return to main menu
- ↑/↓: Navigate in rule list
- Enter: Edit selected rule or execute operation
- ESC: Return to Hook list
- ↑/↓: Navigate between fields and Actions
- Enter: Edit field or Action
- D: Quick delete current rule
- ESC: Return to Hook details
- ↑/↓: Navigate between fields
- Space: Toggle enabled status or type
- Enter: Edit text field
- D: Quick delete current Action
- ESC: Return to rule editing
- Enter: Confirm input
- ESC: Cancel input
The exit code of a Hook command determines the subsequent behavior of the AI workflow. Different exit codes have different semantics:
| Exit Code | Meaning | Behavior |
|---|---|---|
| 0 | Success | Continue workflow normally |
| 1 | Warning | Block current operation, return stderr as substitute result to AI (AI flow continues) |
| 2+ | Critical Error | Block current operation, terminate AI flow, display error to user |
| Exit Code | Tool Executed? | AI Flow | What AI Receives |
|---|---|---|---|
| 0 | Executes normally | Continues | Normal tool result |
| 1 | Blocked | Continues | stderr content (or preset warning if no stderr) |
| 2+ | Blocked | Terminated | AI not called, error displayed to user |
| Exit Code | AI Flow | What AI Receives |
|---|---|---|
| 0 | Continues | Normal tool result |
| 1 | Continues | stderr content replaces original tool result (falls back to stdout if no stderr) |
| 2+ | Terminated | AI not called, error displayed to user |
When exit code is 1:
- If there is stderr output, it is used as the content returned to AI
- If there is no stderr, stdout output is used
- If neither exists, a preset warning message is used
This means you can precisely control the message returned to AI through stderr in your Hook scripts.
#!/bin/bash
# beforeToolCall Hook: Block file modifications during non-working hours
HOUR=$(date +%H)
if [ "$HOUR" -ge 22 ] || [ "$HOUR" -lt 6 ]; then
echo "File modifications are not allowed during non-working hours. Please try again between 6:00-22:00." >&2
exit 1
fi
exit 0#!/bin/bash
# afterToolCall Hook: Detect lint errors after code changes
LINT_OUTPUT=$(npm run lint 2>&1)
if [ $? -ne 0 ]; then
echo "Lint check found issues, please fix the following errors:\n$LINT_OUTPUT" >&2
exit 1
fi
exit 0Hooks configuration is stored in JSON files, with each hook type corresponding to one file:
File Location:
- Global:
~/.snow/hooks/<hookType>.json - Project:
./.snow/hooks/<hookType>.json
File Format:
{
"hookType": [
{
"description": "Rule description",
"matcher": "Tool matcher (only for tool hooks)",
"hooks": [
{
"type": "command",
"command": "Command to execute",
"timeout": 5000,
"enabled": true
}
]
}
]
}{
"afterToolCall": [
{
"matcher": "filesystem-edit",
"description": "Auto run tests after code changes",
"hooks": [
{
"type": "command",
"command": "npm run lint",
"timeout": 15000,
"enabled": true
},
{
"type": "command",
"command": "npm test",
"timeout": 60000,
"enabled": true
}
]
}
]
}{
"beforeToolCall": [
{
"matcher": "filesystem-*",
"description": "Auto backup before file operations",
"hooks": [
{
"type": "command",
"command": "mkdir -p .snow/backups && cp -r . .snow/backups/$(date +%Y%m%d_%H%M%S)/",
"timeout": 30000,
"enabled": true
}
]
}
]
}{
"onUserMessage": [
{
"description": "Log all user requests",
"hooks": [
{
"type": "command",
"command": "echo \"[$(date '+%Y-%m-%d %H:%M:%S')] User message received\" >> .snow/logs/workflow.log",
"timeout": 3000,
"enabled": true
}
]
}
]
}{
"onSubAgentComplete": [
{
"description": "Collect feedback after sub-agent completes",
"hooks": [
{
"type": "prompt",
"prompt": "Sub-agent has completed the task. Please review the results and provide your feedback or next instruction:",
"timeout": 60000,
"enabled": true
}
]
}
]
}{
"toolConfirmation": [
{
"matcher": "terminal-execute",
"description": "Notify team of sensitive operations",
"hooks": [
{
"type": "command",
"command": "curl -X POST $SLACK_WEBHOOK -H 'Content-Type: application/json' -d '{\"text\":\"Sensitive operation pending confirmation\"}'",
"timeout": 5000,
"enabled": true
}
]
}
]
}{
"onSessionStart": [
{
"description": "Check project environment",
"hooks": [
{
"type": "command",
"command": "node --version",
"timeout": 3000,
"enabled": true
},
{
"type": "command",
"command": "git status",
"timeout": 3000,
"enabled": true
},
{
"type": "command",
"command": "npm list --depth=0",
"timeout": 10000,
"enabled": true
}
]
}
]
}- Simple commands: 3000-5000ms
- Build/test: 30000-60000ms
- Interactive Prompt: 30000-60000ms
- Avoid setting too short causing command interruption
- Avoid setting too long affecting workflow
- Avoid overly broad matching (like matching all tools)
- Target specific tools that need special handling
- Use wildcards to simplify configuration:
filesystem-* - Multiple related tools can share rules:
filesystem-read,filesystem-edit
- Ensure commands are available in target environment
- Use absolute paths to avoid environment variable issues
- Consider cross-platform compatibility (Windows/Linux/macOS)
- Use environment variables to store sensitive information (like API keys)
- Only use Prompt when necessary (interrupts workflow)
- Prompt message should be clear and specific
- Provide sufficient context to help user decision-making
- Set reasonable timeout duration
- Each rule focuses on single responsibility
- Use clear descriptions to explain rule purpose
- Related Actions can be placed in the same rule
- Avoid duplicate logic between rules
- Test new configurations in project scope first
- Apply to global scope after confirming correctness
- Use
enabledfield to temporarily disable Actions - Check command output and error logs
- Avoid executing commands that take too long
- Consider using async background tasks
- Don't execute heavy operations in high-frequency hooks (like
onUserMessage) - Use disable feature reasonably to reduce unnecessary execution
Q: Will Hooks affect AI response speed?
A: Yes, to some extent. Hook commands execute synchronously, and the AI flow pauses during command execution. It's recommended to keep hook command execution time within a reasonable range.
Q: Can I access AI context information in Hook commands?
A: Currently Hook commands can only execute standard Shell commands and cannot directly access AI context. You can indirectly pass information through filesystem or environment variables.
Q: What happens when project hooks and global hooks conflict?
A: No conflict, both will execute. Project hooks execute first, then global hooks.
Q: How do I debug Hook commands?
A: It's recommended to manually execute commands in terminal first to ensure correctness, then use them in Hooks. You can also add log output to commands to track execution.
Q: Can Prompt type Actions be called multiple times?
A: No. A rule can only have one Prompt Action and cannot coexist with other Actions. If multiple interactions are needed, create multiple rules.
Q: What happens if a Hook command fails?
A: It depends on the exit code. Exit code 1 blocks the current operation and returns stderr as a substitute result to AI (AI flow continues); exit code 2+ terminates the entire AI flow and displays the error to the user. See the "Exit Code Rules" section for details.
Q: Can I use Linux-style commands on Windows?
A: Not recommended. You should write commands appropriate for the running platform, or use cross-platform tools (like Node.js scripts).
Q: How do I disable a Hook without deleting the configuration?
A: In the Action editing interface, use the Space key to toggle "Enabled Status". Disabled Actions retain configuration but won't execute.
Q: Does Matcher support regular expressions?
A: Currently only supports exact matching and wildcard *, doesn't support full regular expressions.
Q: Can I manually edit configuration files?
A: Yes, but it's recommended to use the configuration interface to ensure correct format. Restart Snow CLI after manual editing to load new configuration.