Skip to content

Hooks

Release status

RightNow Agent is coming soon for Windows, macOS and Linux. Installation commands and workflows in these guides are reference material until the public release is available.

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

Hook events

EventFires when
SessionStartSession starts
UserPromptSubmitPrompt submitted
PreToolUseBefore a tool call; can deny or rewrite it
PostToolUseTool call succeeded
PostToolUseFailureTool call failed
PermissionDeniedPermission system denied a call
StopMain agent completes a turn; can block the stop
StopFailureTurn ends with an API error
StopCancelledTurn is interrupted or cancelled
NotificationUser-attention event
SubagentStartSubagent starts
SubagentStopSubagent turn completes; can block the stop
PreCompactBefore compaction
PostCompactAfter compaction
SessionEndSession ends

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

Define hooks in JSON or TOML

FileScope
~/.rightnow/hooks/*.jsonUser.
<repo>/.rightnow/hooks/*.jsonProject, at the repository root.
~/.rightnow/config.toml, managed_config.toml, requirements.tomlConfig layers. All run; duplicates run once.
hooks/hooks.json in a pluginPlugin.

Save this usable Windows guard as bin/safety-check.ps1. It reads one event from standard input and denies a recursive Remove-Item request; otherwise it exits successfully without output.

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
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "powershell -NoProfile -File bin/safety-check.ps1", "timeout": 10 }
        ]
      }
    ]
  }
}
toml
[[hooks.PreToolUse]]
matcher = "Bash"
hooks = [
  { type = "command", command = "powershell -NoProfile -File bin/safety-check.ps1", timeout = 10 },
]

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.

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 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
{
  "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
{ "decision": "deny", "reason": "Unsafe command detected" }

Rewrite

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.

OutputEffect
{"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 0Allow 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.
  • Exit code 2 denies PreToolUse or blocks Stop and SubagentStop, using stderr as feedback when valid JSON is absent.
  • Other nonzero exits fail open when stdout has no valid blocking decision: the failure is recorded, but the action is not blocked.
Hooks fail open

Timeouts, crashes, malformed output, unset variables, and most nonzero exits are recorded and never block. Only a deny or block decision, or exit 2, does.

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
{ "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; private, link-local, and cloud-metadata addresses are refused (loopback is allowed), and 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 /import-claude imports or dismisses Claude settings.
  • 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.
  • RightNow Agent 1.0.5 runs project hook files without a folder-trust prompt. Review a repository's .rightnow/hooks/, .claude/, and .cursor/ files first; every hook runs with your privileges.

/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.