kubectl on a prod context, to log every command to your audit system, or to add a line of context to every new chat.
A hook can block an action, ask you first, rewrite a tool’s input, or add context. It can never approve something on your behalf. A hook that answers “allow” counts as no opinion, and Kepler’s normal rules decide. Hooks only make Kepler stricter.
Let Kepler write one
The quickest way to make a hook is to describe it:- The hook goes into this workspace’s
.kepler/hooks.jsonby default. A hook for all workspaces needs you to ask for that, and the card says so. - A hook command on the always-blocked list is refused.
- Kepler can add hooks only in Assist and Yolo. In Observe it can explain and list your hooks, not add them.
Add one in Settings
Open Settings > Customize and pick the Hooks chip. New hook builds a hook from fields: when it runs, which tools it applies to, the hook type, a timeout, Block if it fails, and Save for (all workspaces, or one). Each hook has its own switch, and Hooks on at the top switches every hook at once. See Customize.Where hooks live
A workspace hook is trusted by a fingerprint of its exact content. If a
git pull changes it, or adds a new one, it stops running until you review it again. Customize shows a banner when a workspace has hooks waiting for review. The fingerprint covers the hook entry, not a script it calls, so if a hook runs ./guard.sh, review that script too.
Kepler’s own file and shell tools can read these files but never write them. Only you change your hooks.
Hooks you wrote for other agents
Kepler reads the same hook format Claude Code uses, so hooks you already wrote work here unchanged. Kepler finds them in~/.claude/settings.json and a project’s .claude/settings*.json, but never runs them from there. Customize lists them under From Claude Code with an Import button, which copies the hook into the matching Kepler file.
A first hook by hand
This hook asks you before anykubectl command that mentions prod. Put it in ~/.kepler/hooks.json:
~/bin/prod-guard.py:
PreToolUseis the event: before a tool runs."matcher": "Bash"limits it to shell commands.- The script gets the event as JSON on stdin and prints its answer as JSON.
askputs an approval card in front of you that reads “A hook asked: prod context”.
Events
Events that can block may stop or change the action. The rest are for context, logging and alerts.
Kepler adds four events of its own, in the same format:
What a hook receives
A hook gets JSON on stdin. It includessession_id (the chat), transcript_path, cwd, hook_event_name and permission_mode (the posture). Kepler adds kepler_agent. Tool events add tool_name, tool_input, tool_use_id and kepler_tool_name, and tool_response afterwards.
Tool names are mapped so matchers written for Claude Code work. A matcher may use either name.
A matcher made of letters, digits,
_, | and , means exact names, such as Bash|Edit. Anything else is a regular expression searched anywhere in the name, so Edit.* also matches NotebookEdit. Anchor it (^Edit$) when that matters.
Secrets are masked in everything a hook receives and everything it prints.
What a hook can answer
The JSON can carry
decision: "block" with a reason; hookSpecificOutput.permissionDecision (deny or ask) with permissionDecisionReason, updatedInput or additionalContext; continue: false with a stopReason; and systemMessage.
Where Kepler differs from Claude Code:
allowanddeferare ignored. Hooks can only make Kepler stricter.- A hook’s ask is not cleared by your allow rules or by a session allow. Posture, block rules and the always-blocked list still win over everything.
- A rewritten input (
updatedInput) is checked again by Kepler’s rules. - Add
"failClosed": trueto a hook to turn a crash, a timeout or unreadable JSON into a deny. Use it for security hooks. - On runs nobody is watching (watchers, automations, channels), a hook’s ask counts as a deny.
- Context over 10,000 characters is saved to a file and Kepler gets its path. Nothing is cut.
- Tools that run on the model provider’s side, such as native web search, skip hooks.
- When several hooks match, they run in parallel and the strictest answer wins.
UserPromptSubmit, and 600 at most.
Hook types
An
http hook looks like this:
When a hook misbehaves
- Every hook run is logged. See them under Settings > Plan & Usage, on the Hooks tab of Activity, or in
~/.kepler/hooks/log-YYYY-MM-DD.jsonl. - A mistake in
hooks.jsonshows as a banner in Customize. The broken entry is skipped and every other hook keeps running. - If your login shell prints text before your JSON, Kepler reads the last line that is a JSON object.
- A hook in a workspace that stopped running usually changed and needs review again.
Where to go next
Customize
Where hooks, skills, commands, agents and plugins are managed.
Permissions
The rules hooks sit on top of.
Plugins
Plugins can bring hooks too, held to the same rules.
Postures
Observe, Assist and Yolo.