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-hooksDiscovery 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
| Surface | Event | Return contract |
|---|---|---|
| Tool call gate | tool_call | Return { block: true, reason } to refuse the call. reason becomes the error the model sees. First block wins. |
| Tool result rewrite | tool_result | Return { content?, details?, isError? } to mutate what the model receives. Handlers chain. |
| Per-call message redaction | context | Return { messages } to replace the message array sent to the model for this call. Handlers chain. |
| Compaction gate | session_before_compact | Return { cancel: true } to veto compaction. Same shape for session_before_branch, session_before_switch, session_before_tree. |
| Session lifecycle | session_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
blockwins — 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.