pi-yaml-hooks
v2026.5.12
Published
YAML hook automation for the PI coding agent: tool guards, session hooks, prompts, notifications, and bash actions.
Maintainers
Readme
pi-yaml-hooks
Run bash around PI tool calls, block risky commands, and post UI notifications, confirmations, and status entries from one hooks.yaml file. pi-yaml-hooks plugs into the PI coding agent as a package; nothing else to wire up.
This repo is the PI port of OpenCode-Hooks. The hook model is familiar; the runtime is PI-native and the limits are explicit.
What it does
- Run hooks on
tool.before.*,tool.after.*,file.changed,session.created,session.idle, andsession.deleted - Use
bash,tool,notify,confirm, andsetStatusactions - Filter hooks with
matchesCodeFiles,matchesAnyPath, andmatchesAllPaths - Load one global root config and one trusted project root config; imports are gated by trust and opt-in env vars
- Show built-in diagnostics with
/hooks-status,/hooks-validate,/hooks-trust,/hooks-reload, and/hooks-tail-log - Emit structured in-session diagnostics when PI supports custom messages
- Inject a short hook-awareness note before agent start (disable with
PI_YAML_HOOKS_PROMPT_AWARENESS=0)
Quick start
Create a minimal global hook file so you can see the extension working right away.
pi install npm:pi-yaml-hooks
mkdir -p ~/.pi/agent/hook
cat > ~/.pi/agent/hook/hooks.yaml <<'YAML'
hooks:
- event: session.idle
actions:
- notify: "Agent is idle"
YAML
piExpected startup output:
[pi-yaml-hooks] Loaded 1 hook (global: 1, project: 0).If a trusted project also has project hooks, the summary includes both scopes:
[pi-yaml-hooks] Loaded 3 hooks (global: 1, project: 2).Requirements
- macOS or Linux
- Node.js
>=22.0.0 bashon$PATH(override withPI_YAML_HOOKS_BASH_EXECUTABLE)@earendil-works/pi-coding-agent ^0.74.0
Windows is unsupported.
Install
The full install reference, including settings.json edits, project-local installs, npm-library import paths, and local-checkout symlinks, lives in docs/setup.md. The short version:
pi install npm:pi-yaml-hooks # recommended
pi install https://github.com/KristjanPikhof/pi-yaml-hooks # latest unreleased
pi -e npm:pi-yaml-hooks # one-off run, nothing written to settingsAdd -l to pi install to write to project settings (.pi/settings.json) instead of global (~/.pi/agent/settings.json).
SDK compatibility matrix
Before widening PI peer support or merging SDK-sensitive changes, run the repeatable SDK matrix:
npm run compat:sdk-matrixThe matrix checks the supported SDK pair (@earendil-works/pi-coding-agent and @earendil-works/pi-tui 0.74.0). It creates a temporary copy of the repository, installs each SDK pair in that copy only, then runs npm run typecheck and the consumer-facing npm test script (which is the documented public-surface check; it does not exercise the internal dev suite). The working checkout's package.json, package-lock.json, and normal node_modules are not mutated.
For the full internal test suite, run npm run test:internal directly in the working checkout.
To preview the matrix workflow without installing anything:
npm run compat:sdk-matrix:dry-runRuntime PI behavior also has a smoke checklist for surfaces unit tests cannot fully emulate, including slash commands, custom diagnostics, UI actions, follow-up prompts, user_bash, session switching, and /quit:
scripts/smoke/pi-runtime-smoke.shMaintainer-facing details, including the smoke checklist, evidence template, and gating rules, live in docs/maintaining.md. Keep the generated evidence file with release notes or SDK-widening PRs.
Future SDK lines (0.75.x and later) are gated, not part of the current peer range. Try them explicitly with:
npm run compat:sdk-matrix:futureDo not widen support until both the future matrix and the runtime smoke pass, including the no-builtin-tools gate.
How it works
pi-yaml-hooks discovers at most one global root config and one project root config. Project hooks and project-root imports load only when the repo or worktree anchor is trusted. Global-root imports require PI_YAML_HOOKS_ALLOW_GLOBAL_IMPORTS=1, package imports require PI_YAML_HOOKS_ALLOW_PACKAGE_IMPORTS=1, and project imports outside the trust anchor require PI_YAML_HOOKS_ALLOW_PROJECT_IMPORTS_OUTSIDE_TRUST_ANCHOR=1. The project root is repo/worktree-aware, not exact-cwd-only.
When an event matches, pi-yaml-hooks evaluates conditions and runs the configured actions. bash actions receive hook context JSON on stdin plus injected PI_* environment variables such as PI_PROJECT_DIR, PI_WORKTREE_DIR, PI_SESSION_ID, and PI_GIT_COMMON_DIR. At agent start, the extension also appends a short hook-awareness note to the system prompt so PI has the current hook and trust context while it works.
Native PI surface
Events
| Event | Meaning |
|---|---|
| tool.before.* | Before a tool call |
| tool.after.* | After a tool call |
| file.changed | Synthesized after recognized file mutations |
| session.created | PI startup or a genuinely new session |
| session.idle | Agent turn finished and no messages are pending |
| session.deleted | Best-effort cleanup on shutdown or session switch; includes PI's reason (quit, reload, new, resume, or fork) when available |
Actions
| Action | PI behavior |
|---|---|
| bash | Runs a shell command with injected context |
| tool | Sends a follow-up prompt into the current PI session |
| notify | Shows a PI notification when a UI surface exists |
| confirm | Shows a confirmation dialog before a tool runs |
| setStatus | Sets a PI status-bar entry keyed to the hook |
Slash commands
| Command | What it shows |
|---|---|
| /hooks-status | Active hooks, config paths, trust state, and log path |
| /hooks-validate | Validation results for active hooks and skipped untrusted project hooks |
| /hooks-trust | Adds the current repo/worktree anchor to ~/.pi/agent/trusted-projects.json |
| /hooks-reload | Asks PI to reload extensions; edited hooks also refresh lazily on the next relevant event |
| /hooks-tail-log | Log path plus a ready-to-run tail -F command; --follow starts a detached live tail, and --path prints only the path |
/hooks-status, /hooks-validate, and hook-load validation errors also emit structured in-session diagnostics when PI supports custom messages.
PI exposes ctx.ui.addAutocompleteProvider on the supported ^0.74.0 line, so pi-yaml-hooks layers guarded /hooks autocomplete into the editor. Suggestions include the command names plus contextual hook IDs, event names, config paths, and log-tail options where useful. Hook IDs are loaded lazily and memoized by hook-snapshot signature, not fixed at extension registration time.
Important limitations
These are the PI-specific constraints that matter most:
command:actions are unsupported on PI and are rejected at load timetool:is prompt injection, not imperative tool executionaction: stoponly has real effect ontool.before.*runIn: mainis unsupported for non-bashactionssession.deletedis best-effort and intentionally lossy: PI fires it for shutdown and for session switches like/new,/resume, and/fork, andpi-yaml-hooksforwards PI'sreason(quit,reload,new,resume, orfork) on the envelope so hooks can disambiguateuser_bashinterception is opt-in withPI_YAML_HOOKS_ENABLE_USER_BASH=1
Keep those rules in mind when authoring hooks. They explain most surprising behavior.
What trust grants when user_bash is enabled
When PI_YAML_HOOKS_ENABLE_USER_BASH=1 is set, every human ! / !! shell command typed in PI is routed through tool.before.bash hooks before PI executes it. This expands the trust surface significantly:
- Observation: hooks receive the typed command in stdin JSON as
tool_args.command, so a trusted-project bash hook can read the full text of every command you type. - Blocking: a
tool.before.bashhook that exits with code2will prevent the command from running. A misconfigured or malicious hook can silently block commands. - Exfiltration risk: the same bash hook can forward
tool_args.commandto an external service. Only enablePI_YAML_HOOKS_ENABLE_USER_BASH=1if you trust every hook in every trusted project.
pi-yaml-hooks emits a one-time stderr warning on startup listing which trusted projects will have access when this env var is set, and shows a PI UI warning on the first intercepted command when a UI is available. The warning fires once per process and names the projects currently in ~/.pi/agent/trusted-projects.json.
This mode is disabled by default. Agent-generated bash tool calls are always intercepted regardless of this setting.
Config paths and trust
Global root config paths:
~/.pi/agent/hook/hooks.yaml~/.pi/agent/hooks.yaml
Project root config paths:
<project>/.pi/hook/hooks.yaml<project>/.pi/hooks.yaml
Project hooks are gated by trust because they can run arbitrary bash with your user permissions. Trust is evaluated against the repo or worktree anchor, not an arbitrary nested directory string. trusted-projects.json entries must be absolute paths; relative entries such as . are ignored.
Two ways to trust a project:
PI_YAML_HOOKS_TRUST_PROJECT=1 pior use the built-in command:
/hooks-trustExamples
Example workflows live under examples/. Start with examples/README.md for complete example packs, including pre-tool developer guards and post-tool developer feedback hooks.
These packs are opt-in examples, not built-in PI features.
Docs
Full reference and reading order live in docs/README.md.
License
MIT.
