Skip to main content

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

Blocking means the hook can prevent the operation from proceeding. For example, a 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

1

Create the Hooks Directory

In your repository root, create the .openhands directory if it doesn’t already exist:
2

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
Make the script executable:
3

Create hooks.json

Create .openhands/hooks.json to register your hooks:
.openhands/hooks.json
4

Commit to Your Repository

The next time OpenHands works on your repository, the hooks will be active automatically.

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

Matcher Patterns

The matcher field determines which tools trigger the hook (only relevant for PreToolUse and PostToolUse): For hooks that aren’t tool-specific (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).
The following environment variables are also set:

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.
JSON output (optional):

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: This hook setup prevents the agent from finishing if pre-commit, tests, or CI are failing.

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

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 jq for JSON parsing. Hook scripts receive JSON input on stdin. The jq tool is available in the sandbox for parsing fields like tool_input.command.
  • Use environment variables for simple hooks. For PostToolUse hooks that only need the tool name, $OPENHANDS_TOOL_NAME avoids 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": true run in the background and cannot block operations. Use async for logging or telemetry, not for enforcement.

See Also