Where hooks live

~/.omp/agent/hooks/pre/*.ts      # global pre-hooks
~/.omp/agent/hooks/post/*.ts     # global post-hooks
.omp/hooks/pre/*.ts              # project pre-hooks
.omp/hooks/post/*.ts             # project post-hooks

Discovery is non-recursive: files one directory deeper are ignored. From the CLI, --hook <path> loads an explicit file (it’s an alias for --extension).

What you can hook into

SurfaceEventReturn contract
Tool call gatetool_callReturn { block: true, reason } to refuse the call. reason becomes the error the model sees. First block wins.
Tool result rewritetool_resultReturn { content?, details?, isError? } to mutate what the model receives. Handlers chain.
Per-call message redactioncontextReturn { messages } to replace the message array sent to the model for this call. Handlers chain.
Compaction gatesession_before_compactReturn { cancel: true } to veto compaction. Same shape for session_before_branch, session_before_switch, session_before_tree.
Session lifecyclesession_start, session_shutdown, turn_start, turn_end, message_*, tool_execution_*Observational. Return value ignored.

See the HookAPI type for the full event list. HookAPI is the narrow event-handler surface; ExtensionAPI is the superset that also registers commands, tools, and renderers — reach for it the moment you need more than on.

Block rm -rf in bash

A pre-tool hook that refuses a few catastrophic shapes before bash ever runs. The handler returns { block: true, reason } and the agent surfaces reason as the tool error.

// ~/.omp/agent/hooks/pre/guard-rm.ts
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/extensibility/hooks"
 
const DANGER =
  /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*|-[a-zA-Z]*f[a-zA-Z]*r[a-zA-Z]*)\s+(\/|~|\$HOME)(\s|$)/
 
export default function (pi: HookAPI) {
  pi.on("tool_call", (event) => {
    if (event.toolName !== "bash") return
    const cmd = String(event.input.command ?? "")
    if (DANGER.test(cmd)) {
      return { block: true, reason: `Refused: ${cmd.slice(0, 80)}` }
    }
  })
}

The first block wins — ordering across multiple pre-hooks is filesystem-stable but treat the regex as your last line of defence, not your only one.

Redact secrets in tool output

A post-tool hook that rewrites read results to scrub API keys before the model sees them.

// ~/.omp/agent/hooks/post/redact-keys.ts
import type { HookAPI } from "@oh-my-pi/pi-coding-agent/extensibility/hooks"
 
export default function (pi: HookAPI) {
  pi.on("tool_result", (event) => {
    if (event.toolName !== "read" || event.isError) return
    const content = event.content.map((c) =>
      c.type === "text"
        ? { ...c, text: c.text.replaceAll(/API_KEY=\S+/g, "API_KEY=[REDACTED]") }
        : c,
    )
    return { content }
  })
}

Debugging a hook

Run omp -p '/extensions' to confirm the hook loaded and from which path. If it’s missing, the file wasn’t in a discovered directory — move it under ~/.omp/agent/hooks/pre/ or .omp/hooks/pre/, or load it explicitly with --hook /path/to/file.ts. See Prompt templates and Skills for the adjacent customization surfaces, and Context files when you want a static rule injected every turn instead of an active gate.

Ask Docs

AI assistant to help answer questions about the documentation. Answers are read-only and cite docs/source.

Hi! How can I help you with the documentation today? Answers are read-only and cite docs/source.

Ctrl+Enter to send