@drmikecrowe/omp-claude-hooks-bridge
v0.5.0
Published
Bridge Claude Code hooks (.claude/settings.json) into omp (Oh My Pi) extension lifecycle events.
Readme
omp-claude-hooks-bridge
Built on top of upstream. This package reuses the
claude-hooks-bridgeimplementation fromJonghakseo/pi-extension(originally published to npm as@ryan_nookpi/pi-extension-claude-hooks-bridge) as its foundation, ports it to the omp runtime, and enhances it further. Full attribution and the list of enhancements are in Origins below — please credit the upstream authors if you reuse this code.
Bridge Claude Code hooks (.claude/settings.json) into omp (Oh My Pi) extension lifecycle events.
What it does
- Reads
.claude/settings.jsonhooks configuration from the project root - Executes hooks at matching lifecycle events:
- SessionStart →
session_start - UserPromptSubmit →
before_agent_start - PreToolUse →
tool_call(can block / ask for confirmation / rewrite tool input) - PostToolUse →
tool_result - PreCompact →
session_before_compact(can cancel compaction) - Stop →
agent_end(can queue follow-up messages)
- SessionStart →
- Supports matcher patterns (regex or pipe-separated tool names), including
PreCompact'smanual/autotrigger matcher - Maps omp tool names to Claude Code equivalents (
bash→Bash, etc.) - Handles hook JSON output with
permissionDecision/ exit code 2 for blocking - Provides transcript files for Stop and PreCompact hooks
- Injects hook context into the conversation, matching Claude Code semantics:
hookSpecificOutput.additionalContextfromSessionStartandUserPromptSubmit, plus plain stdout from those same two events.SessionStartcontext is queued for the next user prompt (ompsession_starthandlers cannot return context);UserPromptSubmitcontext is injected into the turn it belongs to. Context from other events is not injected — see Not bridgeable on omp.
Release history is in CHANGELOG.md.
Not bridgeable on omp
These Claude Code context-injection paths are not injected today, rather than faked with a mid-turn message:
| Claude behavior | Status | Why |
| --- | --- | --- |
| PreToolUse additionalContext | Not injected (deviation required) | omp's tool_call result has no context field. The only same-turn landing spot is the paired tool_result, which arrives after the tool ran, so the context cannot influence whether the call happens — it can only steer later calls. See the note below. |
| PostToolUse additionalContext | Not injected (fixable) | omp's tool_result result does accept replacement content, and Claude Code injects this context post-execution too, so bridging it would be faithful rather than a deviation. Nothing blocks it; it is simply not wired up yet. |
| SubagentStart | Not bridgeable | omp has no subagent_* event, and session_stop never fires for subagents, so there is nothing to hook. |
PreToolUse hooks still run, and their allow / ask / deny decisions and
hookSpecificOutput.updatedInput rewrites are still honored — only the
additionalContext field is dropped.
On attaching PreToolUse context to the paired tool_result — this is
mechanically possible (ToolResultEventResult.content replaces the content
array, and ToolResultEvent.content exposes the original to append to), and
for advisory hooks such as a routing nudge it lands in the right turn and
fires only on the matching tool call. It is still a real semantic deviation:
the context arrives after execution, so a hook written as a precondition
("don't run this without X") reads as a post-hoc note, and the model sees the
text as part of the tool's own output unless it is explicitly labelled. The
decision is therefore: not while the same steering need is served faithfully
by PostToolUse additionalContext (row above), which arrives at the same
point in the conversation and is what Claude Code itself does. If you have a
PreToolUse hook whose only job is steering, prefer configuring it as
PostToolUse.
Tool-input rewriting
A PreToolUse hook that returns hookSpecificOutput.updatedInput replaces the
arguments the tool runs with, matching omp's tool_call input contract:
{ "hookSpecificOutput": { "hookEventName": "PreToolUse",
"updatedInput": { "command": "rtk git status", "description": "check status" } } }Semantics follow omp: a block decision short-circuits ahead of any rewrite,
the last non-blocking rewrite wins when several hooks match, the returned
object replaces the raw arguments rather than merging into them, and hooks
do not see earlier hooks' revisions. omp ignores input replacement for
computer calls.
Compaction hooks
PreCompact maps to omp's session_before_compact. Hooks receive the common
fields plus trigger, custom_instructions, and transcript_path:
{ "hook_event_name": "PreCompact", "trigger": "manual",
"custom_instructions": "keep the auth work", "transcript_path": "/tmp/..." }trigger is manual for /compact and auto for omp's automatic
compaction, derived from omp's auto_compaction_start / auto_compaction_end
events which bracket the automatic path. It doubles as the matcher value, so
"matcher": "auto" runs a hook only on automatic compaction.
custom_instructions carries what the user passed to /compact, and is empty
on the auto path — matching Claude Code.
A deny / block decision or exit code 2 cancels the compaction, which is
what Claude Code's exit-2 semantics do for this event. Claude Code's
PostCompact has an omp analogue (session_compact) but is not bridged yet.
Requirements
- bash is required — hooks are executed via
bash -lc. Works on macOS and Linux. Not natively supported on Windows.
Security
This extension executes shell commands found in any .claude/settings.json
it discovers — the current project's (<cwd>/.claude/settings.json), your
home config ($CLAUDE_CONFIG_DIR/settings.json, default ~/.claude/settings.json),
~/.claude.json, and installed plugin hooks.json files — with the same
privileges as the omp process itself (bash -lc <command>, full environment).
Only PreToolUse "ask"/"block" decisions go through a user confirmation
prompt. SessionStart, UserPromptSubmit, PostToolUse, and Stop hooks
run immediately and unconditionally the moment their lifecycle event fires —
SessionStart hooks run before you've typed anything.
A PreToolUse hook can rewrite the arguments a tool runs with, and that
rewrite is not confirmed. updatedInput replaces the command before it
executes, so an untrusted hook can turn an approved command into a different
one. Review rewriting hooks with the same care as blocking ones.
Opening or cd-ing into a directory whose .claude/settings.json you do
not trust is equivalent to running its shell commands. Treat an untrusted
repository containing hook configuration the same way you'd treat an
untrusted shell script — do not open it with this extension active unless
you've reviewed .claude/settings.json first.
Install
omp plugin install @drmikecrowe/omp-claude-hooks-bridgeOrigins
This package is not a from-scratch implementation — it reuses and builds
on upstream work. The entire hook-bridging design (settings discovery,
matcher logic, hook execution, decision extraction, transcript handling)
originates from the claude-hooks-bridge package in the
Jonghakseo/pi-extension
monorepo, originally published to npm as
@ryan_nookpi/pi-extension-claude-hooks-bridge.
All credit for the original design and implementation belongs to that
project's authors.
What was reused as-is
- The core hook lifecycle model (
SessionStart/UserPromptSubmit/PreToolUse/PostToolUse/Stop) and its mapping onto extension events. - Settings discovery and merge order across project and home
.claude/settings.json. - Matcher-pattern matching, tool-name aliasing, and hook decision extraction
(
permissionDecision, exit-code-2 blocking). - Transcript-file generation for
Stophooks.
What this fork ports and adds on top
- Runtime port: targets omp extension packaging (
omp.extensionsmanifest,omp plugin install) and builds against the@oh-my-pi/pi-coding-agentextension SDK instead ofpi's. CLAUDE_CONFIG_DIRsupport: home-config resolution (settings.json,installed_plugins.json) now honors theCLAUDE_CONFIG_DIRenvironment variable, matching Claude Code's own documented config-dir override, instead of hardcoding~/.claude.- Hook context injection: upstream executed hooks but discarded whatever
context they emitted, so hooks whose only purpose is to steer the agent
were effectively no-ops. This fork injects it, matching Claude Code
semantics —
hookSpecificOutput.additionalContextfrom any event, plus plain stdout fromSessionStart/UserPromptSubmit— routed through whichever omp API each event supports (pi.sendMessagewithdeliverAs: "nextTurn"forsession_start, a returnedmessageforbefore_agent_start). See Not bridgeable on omp for the two paths that have no omp equivalent. PreCompactbridging: upstream mapped no omp event to Claude Code'sPreCompact. It now maps tosession_before_compact, with themanual/autotrigger, matcher support, and cancel-on-block.- Tool-input rewriting: upstream read only the block/ask decision from
PreToolUseresults and droppedhookSpecificOutput.updatedInput, so command-rewriting hooks silently did nothing. This fork returns the rewrite as omp'stool_callinput, stripping the path aliases the payload normalizer adds so the replacement keeps the tool's own key shape. - Security hardening: transcript files and their directory are now
created with restrictive
0600/0700permissions instead of umask defaults, so session transcripts aren't world-readable on shared hosts; the plugin project-scope check is now path-boundary-safe instead of a raw prefix match, so a sibling directory can no longer inherit another project's plugin hooks. - Documentation: a
Securitysection spelling out exactly which hook events execute unconfirmed shell commands and when, plus this expanded attribution.
Naming and documentation are rebranded from pi to omp throughout, and
the package is republished under the @drmikecrowe namespace as
@drmikecrowe/omp-claude-hooks-bridge, distributed under the same MIT
license as upstream (see LICENSE, which carries forward
the original copyright notice).
If you fork or reuse this code further, please preserve this attribution
chain back to Jonghakseo/pi-extension.
