Write a hook
Step-by-step tutorial that blocks access to a dummy .env file.
In this tutorial you author one local Hook, enable it, watch it block a matching tool call, inspect the Execution Report, and remove the disposable project.
The Hook is a locally authored version of the dependency-free no-env-access
policy from HooKit’s Core catalog.
Its exact shell: "false" decision is handled without a subprocess
or any external command-line dependency.
Prerequisites
- HooKit installed for your user by following Installation.
- A terminal in a directory where you can create one disposable folder.
- Permission to trust that disposable project for this tutorial.
What you build
A Hook named no-env-access that blocks the read and write tools when
their path identifies a .env file:
| Field | Value | What it does |
|---|---|---|
| name | no-env-access | Identifies the Hook in your Catalog |
| description | Blocks reads and writes to .env files. | Summarizes the Hook in /hooks |
| event | tool_call | Subscribes before a Pi tool runs |
| filter | toolName is read/write and path ends in .env | Selects the matching calls |
| shell | false | Produces the block decision |
Security: hook shells execute locally as trusted code with your Pi process permissions. They are not sandboxed. The 5-second default timeout bounds commands; it is not a sandbox. Use only the dummy value below—never a real secret.
Step 1—create the project and dummy file outside Pi
Stay in your terminal. Do not start Pi yet. Create a new disposable project
and its harmless .env before any guard is enabled:
mkdir hookit-first-hook
cd hookit-first-hook
mkdir .pi
printf 'DUMMY_KEY=dummy-value\n' > .envExpected result: your current directory is
hookit-first-hook;.envcontains onlyDUMMY_KEY=dummy-value; and.pi/is empty.
Step 2—Create the project configuration
Still outside Pi, create .pi/hookit.json with this content:
{
"$schema": "https://raw.githubusercontent.com/meffmadd/HooKit/main/schema.json",
"local": {
"no-env-access": {
"description": "Blocks reads and writes to .env files.",
"event": "tool_call",
"filter": {
"toolName": ["^read$", "^write$"],
"path": "(^|/)\\.env$"
},
"shell": "false"
}
}
}Expected result:
.pi/hookit.jsonis valid JSON and the dummy.envalready exists before HooKit can enforce the policy.
The fields form one decision: for a tool_call, both Filter fields must match;
then exact false produces block with code 1. Both exact true and exact
false are optimized shortcuts. Every other shell string—for example
git diff --quiet—runs through the real local shell. See filter
for exact regular-expression semantics.
Step 3—start Pi with project trust
HooKit reads project .pi/hookit.json only after Pi trusts the project. Start
the tutorial session with Pi’s one-run trust flag:
pi --approveExpected result: Pi opens in
hookit-first-hookwith the project trusted for this run, so the globally installed HooKit extension can load the project Hook Catalog.
Project trust controls whether HooKit may read project hooks; it is not a sandbox. The security page describes the boundary.
Step 4—enable the hook
Inside Pi:
- Open the Hook panel with
/hooks. - Use the arrow keys to focus
no-env-accessunderlocal. - Press Enter once.
Expected result:
no-env-accessis shown as directly enabled.
Configuration alone does not enable a Hook. This tutorial uses direct
enablement so the decision is explicit. The /hooks reference
documents the rest of the panel without interrupting this path.
Step 5—Trigger the guarded read
Close the panel, then send Pi this prompt exactly:
Read .env with the read tool. Do not use bash or another tool.Expected result: HooKit blocks the
readtool call before it runs. The transcript shows the block, and the dummy file contents are not returned.
This Hook intentionally matches only the read and write tool names. The
prompt names read explicitly so the exercise follows the Filter you authored.
If the contents appear, confirm the project was started with pi --approve,
the Hook is enabled, and the prompt was copied exactly.
Step 6—inspect the execution report
The blocked tool call appends a collapsed HooKit Execution Report entry to the transcript. Expand it using the hint shown on that entry.
Expected result: the flat report contains one
✗Hook row forlocal/no-env-accesswith its duration. The surrounding tool outcome is the block decision. There is no Action row because this Hook owns no Action.
Tool calls and their results in one tool lifecycle join one Execution Wave with a single end-to-end duration. Execution Reports documents the row and duration boundaries.
Step 7—clean up
Quit Pi with /quit. Then use the terminal to remove only the files and empty
directories created by this tutorial:
rm .env .pi/hookit.json
rmdir .pi
cd ..
rmdir hookit-first-hookExpected result: the disposable
hookit-first-hookdirectory no longer exists. The globally installed HooKit package remains available for real projects.
Next steps
You have configured, enabled, exercised, observed, and cleaned up a Hook. Continue with one of these pages:
- Explore the Hook library for ready-made file safety, Bash, Git, tool restriction, quality, notification, and observability policies.
- Configuration for the complete persisted format.
- Hook Evaluation for the distinction between Hook and Event Outcomes.