# Hooks

[Docs](https://www.rightnowai.co/docs) / [RightNow Agent](https://www.rightnowai.co/docs/agent) / [Hooks](https://www.rightnowai.co/docs/hooks)

Updated 2026-10-17

> **Release status**
>
> RightNow Agent, the terminal coding agent, is paused. Its guides stay online as reference.

Hooks run a command or send an HTTPS request when a session, turn, tool call, subagent, or compaction event fires.

## Define hooks in JSON or TOML

Save this Windows example as `bin/safety-check.ps1`. It reads one event from standard input and returns a deny decision when `run_terminal_command` contains `Remove-Item` followed by `-Recurse`. Other valid inputs produce no output.

**PowerShell**

```powershell
$hookPayload = [Console]::In.ReadToEnd() | ConvertFrom-Json
$command = [string]$hookPayload.toolInput.command

if ($hookPayload.toolName -eq "run_terminal_command" -and $command -match "(?i)\bRemove-Item\b.*-Recurse\b") {
  @{ decision = "deny"; reason = "Recursive removal requires manual review" } | ConvertTo-Json -Compress
}
```

The next two blocks are configuration templates for that script. Choose JSON or TOML; do not install both copies of the same hook.

**JSON**

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "powershell -NoProfile -File bin/safety-check.ps1", "timeout": 10 }
        ]
      }
    ]
  }
}
```

**TOML**

```toml
[[hooks.PreToolUse]]
matcher = "Bash"
hooks = [
  { type = "command", command = "powershell -NoProfile -File bin/safety-check.ps1", timeout = 10 },
]
```

**Hook definition files**

| File | Scope |
| --- | --- |
| ~/.rightnow/hooks/\*.json | User. |
| &lt;repo&gt;/.rightnow/hooks/\*.json | Project, at the repository root. |
| ~/.rightnow/config.toml, managed\_config.toml, requirements.toml | Config layers. All run; duplicates run once. |
| hooks/hooks.json in a plugin | Plugin. |

Handler fields: `type` (`command` or `http`), `command`, `url`, `env`, and `timeout` in seconds (default 5; 600 for Stop and SubagentStop). A direct executable path in a JSON hook file resolves relative to that file.

The inline PowerShell command above starts in the workspace root, so its `bin/safety-check.ps1` path is workspace-relative.

## Hook events

**Hook events**

| Event | Fires when |
| --- | --- |
| SessionStart | Session starts |
| UserPromptSubmit | Prompt submitted |
| PreToolUse | Before a tool call; can deny or rewrite it |
| PostToolUse | Tool call succeeded |
| PostToolUseFailure | Tool call failed |
| PermissionDenied | Permission system denied a call |
| Stop | Main agent completes a turn; can block the stop |
| StopFailure | Turn ends with an API error |
| StopCancelled | Turn is interrupted or cancelled |
| Notification | User-attention event |
| SubagentStart | Subagent starts |
| SubagentStop | Subagent turn completes; can block the stop |
| PreCompact | Before compaction |
| PostCompact | After compaction |
| SessionEnd | Session ends |

`SubagentEnd` is accepted as an alias for `SubagentStop`. Every other event only observes.

## Matcher rules

- An empty or omitted matcher, or `*`, matches every invocation in its group. A plain name or `|` list made only of letters, digits, and underscores matches only a value equal to one of its terms; any other pattern is an unanchored regular expression.
- Tool events match the resolved tool name, notifications match their type, and subagent events match the subagent type. MCP calls match as `server__tool`, so `github` matches no MCP tool while `^github__` matches every tool of that server.
- `Stop` and `UserPromptSubmit` ignore matchers with a warning.
- Claude-style matcher names such as Bash, Read, Edit, Write, Grep, and WebSearch map to RightNow Agent tool names while retaining their original match.

## Read the stdin envelope

Each event sends camelCase common fields plus its own. This `PreToolUse` example is complete; transcript, client, and subagent fields appear only when available.

**JSON**

```json
{
  "hookEventName": "pre_tool_use",
  "sessionId": "session-id",
  "cwd": "C:\\work\\project",
  "workspaceRoot": "C:\\work\\project",
  "timestamp": "2026-04-14T12:00:00Z",
  "promptId": "prompt-id",
  "permissionMode": "default",
  "toolName": "run_terminal_command",
  "toolUseId": "tool-use-id",
  "toolInput": { "command": "rightnow doctor", "description": "Check terminal support" },
  "toolInputTruncated": false
}
```

## Deny or rewrite a tool call

A PreToolUse hook allows, denies, or rewrites the call. Emit one JSON response on stdout for each invocation; no output allows it.

Deny

**JSON**

```json
{ "decision": "deny", "reason": "Unsafe command detected" }
```

Rewrite

**JSON**

```json
{
  "hookSpecificOutput": {
    "updatedInput": { "command": "rightnow doctor", "description": "Check terminal support" }
  }
}
```

- `updatedInput` must be a JSON object. It replaces the whole input that plan mode, the permission prompt, and the tool see, so `run_terminal_command` needs both `command` and `description`.
- The last valid rewrite wins, a deny discards every rewrite, and a rewrite that fails the tool schema is blocked as invalid input.
- Any other `decision` value is an error, and the hook fails open.

## Gate completion with Stop

A Stop or SubagentStop hook decides whether the turn may end.

**Stop hook outputs**

| Output | Effect |
| --- | --- |
| `{"decision": "block", "reason": "..."}` | Keep working. The reason is fed back to the model in the same turn. |
| `{"hookSpecificOutput": {"additionalContext": "..."}}` | Keep working, shown as feedback rather than an error. |
| `{"continue": false, "stopReason": "..."}` | End the turn, overriding any block. |
| `No output, exit 0` | Allow the stop. |

Input adds `stopHookActive` (true during a continuation), `lastAssistantMessage`, and `reason`. Interrupts and API errors fire `StopCancelled` and `StopFailure` instead, and the session-end Stop cannot block. After eight continuations in one turn, the turn ends.

## Exit-code behavior

- Exit code 0 allows the action when no blocking JSON decision is returned.
- For `PreToolUse`, a valid JSON deny is honored on any exit code; a valid allow response does not override exit code 2. For `Stop` and `SubagentStop`, valid decision JSON wins; without it, exit code 2 blocks and supplies stderr as feedback.
- Other nonzero exits fail open when stdout has no valid blocking decision: the failure is recorded, but the action is not blocked.

> **Warning: Hooks fail open**
>
> Timeouts, crashes, malformed output, and missing required variables fail open. A rewrite that fails the tool schema still blocks the call. Nonzero exits follow the rules above.

## Hook environment

Every command hook receives `RIGHTNOW_HOOK_EVENT`, `RIGHTNOW_HOOK_NAME`, `RIGHTNOW_SESSION_ID`, `RIGHTNOW_WORKSPACE_ROOT`, and the compatibility alias `CLAUDE_PROJECT_DIR`. These names are reserved and handler `env` values cannot override them.

Handler `command` and `url` fields expand `${VAR}` and `$VAR` when the hook runs. A shell command that references an unset variable without a default does not run.

## HTTP hooks

Template: replace the URL with an endpoint you control before adding this handler to a hook group.

**JSON**

```json
{ "type": "http", "url": "https://hooks.example.com/rightnow-event", "timeout": 15 }
```

HTTP hooks POST the full event envelope as JSON. Only `https://` URLs are accepted. The initial URL check refuses resolved private, link-local, and cloud-metadata addresses (loopback is allowed); the request resolves hostnames again. Redirects are not followed. A gate reads its decision from the response body.

## Claude Code and Cursor compatibility

- Claude Code hooks load from `settings.json` and `settings.local.json` in `~/.claude/` and the project's `.claude/` (`[compat.claude] hooks`, on by default) until an import or dismissal through `/import-claude` saves `[claude_compat] imported = true`.
- Cursor hooks load from `~/.cursor/hooks.json` and the project's `.cursor/hooks.json` (`[compat.cursor] hooks`, on by default); camelCase event names map to RightNow events.
- Input keys are camelCase, such as `stopHookActive` and `toolResult`, and `UserPromptSubmit` is observe-only, so a ported prompt-blocking hook stops blocking.
- A local build without a `GROK_VERSION` stamp does not prompt for folder trust. A stamped build applies its folder-trust settings. Review the repository's hook files before running them; command hooks run as your user.

## Next task

`/hooks` opens the Hooks tab. If a hook does not run, confirm its source and status in the Hooks tab, then inspect the failure entry in the session scrollback or logs and continue with [troubleshooting](https://www.rightnowai.co/docs/troubleshooting).

## Previous

[MCP Servers](https://www.rightnowai.co/docs/mcp)

## Next

[Skills](https://www.rightnowai.co/docs/skills)
