Guides Automation
Hooks and Events
This guide walks through common automation patterns using Autohand Code hooks and events. You will configure shell commands that run when files change, tools run, and sessions end.
Before you start
Make sure you have Autohand Code installed and know where your config lives: ~/.autohand/config.json by default, or the file passed with --config. Hook definitions go in the hooks.hooks array; each names an event and a command. Every event is listed in the Hooks Reference.
| Category | Example events | Use case |
|---|---|---|
| Session | session-start, session-end, pre-clear | Initialize context and send wrap-up notifications. |
| Prompt and turn | pre-prompt, stop | Screen instructions and monitor token usage per turn. |
| Tool | pre-tool, post-tool | Log tool usage, gate shell commands, and validate results. |
| File | file-modified | Run linters, formatters, and related tests. |
| Permission and notification | permission-request, permission-denied, notification | Automate approval decisions and surface system alerts. |
| Rate limit and error | session-error, rate-limit | Alert operators and write failure logs. |
| Auto-mode | automode:start, automode:iteration, automode:complete | Track autonomous loops and iteration counts. |
| Sub-agent and team | subagent-start, subagent-stop, task-completed | Coordinate multi-agent workflows. |
| Review, mode, and context | review:completed, mode-change, context:compact | Record review outcomes and context lifecycle changes. |
The original on_*, before_*, and after_* names (for example on_file_change and before_tool_call) remain supported and are rewritten onto these events; see Legacy event names in the Hooks Reference for the full mapping.
Tip: Run /hooks in a session to browse every event with its installed and active hook counts, or /hooks list to print the table.
Step 1: lint changed files
Add a file-modified hook that runs the formatter, linter, or test command for the language used in the changed file. Scope it with a filter.path glob so it only fires for that language.
JavaScript
{
"hooks": {
"hooks": [
{
"event": "file-modified",
"command": "npx eslint {{file}} --fix && npx prettier --write {{file}}",
"description": "Lint and format JavaScript",
"filter": {
"path": [
"**/*.js",
"**/*.jsx"
]
}
}
]
}
}
TypeScript
{
"hooks": {
"hooks": [
{
"event": "file-modified",
"command": "npx eslint {{file}} --fix && npx prettier --write {{file}}",
"description": "Lint and format TypeScript",
"filter": {
"path": [
"**/*.ts",
"**/*.tsx"
]
}
},
{
"event": "file-modified",
"command": "npm test -- --findRelatedTests {{file}}",
"description": "Run related tests",
"filter": {
"path": [
"**/*.ts",
"**/*.tsx"
]
},
"timeout": 30000
}
]
}
}
Python
{
"hooks": {
"hooks": [
{
"event": "file-modified",
"command": "ruff check --fix {{file}} && ruff format {{file}}",
"description": "Lint and format Python",
"filter": {
"path": [
"**/*.py"
]
}
},
{
"event": "file-modified",
"command": "pytest -q",
"description": "Run tests",
"filter": {
"path": [
"**/*.py"
]
},
"timeout": 60000
}
]
}
}
Go
{
"hooks": {
"hooks": [
{
"event": "file-modified",
"command": "gofmt -w {{file}} && go test ./...",
"description": "Format and test Go",
"filter": {
"path": [
"**/*.go"
]
},
"timeout": 60000
}
]
}
}
Java
{
"hooks": {
"hooks": [
{
"event": "file-modified",
"command": "./mvnw -q test",
"description": "Run Maven tests",
"filter": {
"path": [
"**/*.java"
]
},
"timeout": 120000
}
]
}
}
Swift
{
"hooks": {
"hooks": [
{
"event": "file-modified",
"command": "swift test",
"description": "Run Swift tests",
"filter": {
"path": [
"**/*.swift"
]
},
"timeout": 120000
}
]
}
}
curl
{
"hooks": {
"hooks": [
{
"event": "session-end",
"command": "curl -X POST \"$SLACK_WEBHOOK\" -H 'Content-type: application/json' -d \"{\\\"text\\\": \\\"Autohand session ended after ${HOOK_DURATION}ms\\\"}\"",
"description": "Slack notification on session end",
"async": true
}
]
}
}Step 2: run related tests
Run tests that are related to the changed file. This keeps feedback fast. Test runs usually exceed the 5-second default, so set a timeout.
{
"hooks": {
"hooks": [
{
"event": "file-modified",
"command": "npm test -- --findRelatedTests {{file}}",
"description": "Run related tests",
"filter": {
"path": [
"src/**/*"
]
},
"timeout": 30000
}
]
}
}Step 3: notify on session end
Send a Slack message when the session finishes. Store the webhook URL in an environment variable and mark the hook async so it never delays shutdown.
{
"hooks": {
"hooks": [
{
"event": "session-end",
"command": "curl -X POST \"$SLACK_WEBHOOK\" -H 'Content-type: application/json' -d \"{\\\"text\\\": \\\"Autohand session ended after ${HOOK_DURATION}ms\\\"}\"",
"description": "Slack notification on session end",
"async": true
}
]
}
}Step 4: block dangerous commands
Use a synchronous pre-tool hook as a guardrail. Print {"decision": "deny"} to reject the command without prompting, or exit with code 2 to stop it with the stderr message.
{
"hooks": {
"hooks": [
{
"event": "pre-tool",
"command": "~/.autohand/hooks/block-dangerous.sh",
"description": "Block destructive shell commands",
"matcher": "^run_command$"
}
]
}
}With ~/.autohand/hooks/block-dangerous.sh:
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
if [[ "$COMMAND" =~ rm.*-rf.*/ ]]; then
echo '{"decision": "deny", "reason": "Recursive delete of a root path is not allowed"}'
exit 0
fi
exit 0Step 5: audit tool usage
Log every tool call with a timestamp, outcome, and duration for compliance.
{
"hooks": {
"hooks": [
{
"event": "post-tool",
"command": "echo \"$(date -u +%FT%TZ) | $HOOK_TOOL | success=$HOOK_SUCCESS | ${HOOK_DURATION}ms\" >> ~/.autohand/audit.log",
"description": "Audit tool calls"
}
]
}
}Next steps
Explore the Hooks Reference for the full event list, environment variables, and control-flow responses, and see Building Event-Driven Agents to trigger agents from external systems.