Overview
Hooks let you run custom shell scripts at key moments during an OpenHands session. They are configured per-repository via a.openhands/hooks.json file and work across Cloud, CLI, and local GUI setups. The hooks format is
compatible with Claude Code hooks, so you can reuse hook scripts across both tools.
Common use cases include:
- Blocking dangerous commands before execution (e.g., preventing
rm -rf /) - Enforcing quality gates before the agent finishes (e.g., requiring linting or tests to pass)
- Logging and auditing tool usage for compliance
- Injecting context into user prompts (e.g., appending git status)
Hook Types
| Hook | When It Runs | Can Block? |
|---|---|---|
PreToolUse | Before the agent executes a tool | Yes |
PostToolUse | After a tool finishes executing | No |
UserPromptSubmit | Before a user message is processed | Yes |
Stop | When the agent tries to finish | Yes |
SessionStart | When a conversation begins | No |
SessionEnd | When a conversation ends | No |
Stop hook can force the agent
to keep working if linting checks haven’t passed yet. Note that hooks with "async": true run in the background and
can never block, regardless of event type.
Quick Start
Create the Hooks Directory
In your repository root, create the.openhands directory if it doesn’t already exist:Write a Hook Script
Create a shell script for your hook. For example, a Stop hook that requires linting to pass before the agent can finish:.openhands/hooks/lint_check.sh
Commit to Your Repository
Configuration Reference
hooks.json Format
The .openhands/hooks.json file maps hook event types to matchers and commands using snake_case keys:
Claude Code Compatibility
The hooks format is compatible with Claude Code hooks. PascalCase event keys (e.g.,PreToolUse) and the {"hooks": {...}} wrapper are both supported, so you can share hook scripts
between the two tools. The main differences are the file location (.openhands/hooks.json vs .claude/settings.json)
and tool names (e.g., terminal vs Bash).
Hook Definition Fields
| Field | Type | Default | Description |
|---|---|---|---|
type | string | "command" | Hook type (currently only "command" is supported) |
command | string | (required) | Shell command or path to script to execute |
timeout | integer | 60 | Maximum execution time in seconds |
async | boolean | false | Run in background without waiting for result |
Matcher Patterns
Thematcher field determines which tools trigger the hook (only relevant for PreToolUse and PostToolUse):
| Pattern | Description | Example |
|---|---|---|
* | Matches all tools | "matcher": "*" |
| Exact name | Matches a specific tool | "matcher": "terminal" |
| Regex | Auto-detected or wrapped in / | "matcher": "/terminal|browser/" |
Stop, UserPromptSubmit, SessionStart, SessionEnd), set the matcher to "*" or omit it.
How Hook Scripts Work
Input
Hook scripts receive a JSON payload on stdin with details about the event:Additional fields may be present depending on the hook event type (e.g.,
tool_response for PostToolUse, message for UserPromptSubmit).| Variable | Description |
|---|---|
OPENHANDS_EVENT_TYPE | The hook event type (e.g., PreToolUse) |
OPENHANDS_TOOL_NAME | The tool being used (for tool hooks) |
OPENHANDS_PROJECT_DIR | The project working directory |
OPENHANDS_SESSION_ID | The current session ID |
Output
Hook scripts communicate results through exit codes and optional JSON on stdout: Exit codes:0- Success. The operation proceeds normally.2- Block. The operation is denied.- Any other code - Error. The operation proceeds, but the error is logged.
| Field | Description |
|---|---|
decision | "allow" or "deny" - overrides the exit code |
reason | Human-readable explanation shown in the UI |
additionalContext | Additional context injected into the agent’s prompt |
Real-World Example
The OpenHands Agent SDK repository uses a Stop hook that runs pre-commit checks, targeted pytest suites, and GitHub CI status verification before allowing the agent to finish:.openhands/hooks.json- hook configuration.openhands/hooks/on_stop.sh- the stop hook script
More Examples
Block Dangerous Commands (PreToolUse)
Prevent the agent from running destructive shell commands:.openhands/hooks/block_dangerous.sh
.openhands/hooks.json
Enforce Linting Before Stop
Don’t let the agent finish until linting passes - this is what prevents linting errors from being committed:.openhands/hooks/lint_on_stop.sh
.openhands/hooks.json
Log All Tool Usage (PostToolUse)
Log every tool the agent uses for auditing:.openhands/hooks/log_tools.sh
.openhands/hooks.json
Inject Git Context (UserPromptSubmit)
Automatically include git status when the user asks about code changes:.openhands/hooks/inject_git_context.sh
Combine Multiple Hooks
You can configure multiple hook types and multiple hooks per event:.openhands/hooks.json
Viewing Active Hooks
- CLI
- Cloud / Web
Use the
/skills command in the CLI to see loaded skills, hooks, and MCPs for the current session.Tips
- Keep hooks fast. Hooks run synchronously (unless marked
async) and add latency to agent actions. Use reasonable timeouts. - Use
jqfor JSON parsing. Hook scripts receive JSON input on stdin. Thejqtool is available in the sandbox for parsing fields liketool_input.command. - Use environment variables for simple hooks. For PostToolUse hooks that only need the tool name,
$OPENHANDS_TOOL_NAMEavoids the need for JSON parsing. - Test hooks locally. You can test hook scripts by piping JSON to them:
- Async hooks can’t block. Hooks with
"async": truerun in the background and cannot block operations. Use async for logging or telemetry, not for enforcement.
See Also
- Repository Customization - Setup scripts and pre-commit hooks
- Skills - Extend agent behavior with prompt-based skills
- Hooks (SDK Guide) - Programmatic hooks for SDK developers

