This page describes a feature that is still in beta. Behavior, flags, and configuration may change before the next stable release.
Hooks
Hooks let you run a shell command when something happens inside Friday: a turn starts, a tool call finishes, an approval prompt appears. Friday writes a small JSON payload to the command's stdin and moves on. Use them for desktop notifications, logging, or nudging an external tool.
Hooks observe. They cannot block, deny, or change what they fire on.
What you can hook into
| Event | Fires when |
|---|---|
UserPromptSubmit | A turn starts and the agent begins working on your message. |
PostToolUse | A tool call finishes, whether it succeeded or failed. |
Stop | The agent finishes responding to a turn. |
PermissionRequest | Friday is about to ask you to approve something. |
PermissionResolved | An approval prompt resolved — you approved it, you rejected it, or it errored. |
SessionEnd | Friday is exiting. Fires once per run, on quit, Ctrl+C, or a crash. |
SessionEnd tracks the Friday process, not individual threads. Closing, merging, or abandoning a single thread does not fire it.
Where the config lives
| Scope | Path |
|---|---|
| Your machine, every project | ~/.agents/hooks/hooks.json |
| One project | <repo>/.agents/hooks/hooks.json |
| A plugin | <plugin>/hooks/hooks.json |
Friday also reads .claude/hooks/hooks.json in either location, so a repo already set up for Claude Code keeps working.
Every location that exists contributes hooks, and their entries combine — a project file adds to your global one rather than replacing it. A missing file does nothing. A file with malformed JSON logs a warning and contributes no hooks.
Config format
A hooks file maps an event name to a list of matcher groups. The top-level hooks key is optional.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "terminal-notifier -message 'Friday edited a file'"
}
]
}
],
"PermissionRequest": [
{
"hooks": [
{
"type": "command",
"command": "terminal-notifier -message 'Friday needs approval'"
}
]
}
]
}
}
Each matcher group takes two fields:
| Field | Required | What it does |
|---|---|---|
matcher | No | Narrows which events run the command. A string matches the tool name as a glob ("Edit", "Github*"). An object matches payload fields, e.g. { "tool_name": "Bash", "agent_kind": "worker" }, and every key must match. Omit it to run on every event of that type. |
hooks | Yes | The commands to run when the group matches. |
Each command entry takes two more:
| Field | Required | What it does |
|---|---|---|
type | No | "command", or leave it out. Friday skips entries of any other type. |
command | Yes | The shell command to run. In a plugin's hooks file, ${CLAUDE_PLUGIN_ROOT} expands to that plugin's directory. |
Friday skips an entry whose event name it doesn't recognize and logs a warning.
What the command receives
Friday writes one JSON object to the command's stdin:
{
"event": "PostToolUse",
"tool_name": "Edit",
"agent_kind": "worker",
"thread_id": "a1b2c3"
}
agent_kind is orchestrator for Dispatch, worker for a thread, or chat or classic depending on mode. (Dispatch and "orchestrator" are the same thing — see the glossary.)
That is the whole payload. Friday leaves out tool arguments and tool results on purpose, because they can carry file contents, credentials, and API responses. A hook can see that an Edit ran; it cannot see what was written.
To inspect the payload, read stdin:
{ "type": "command", "command": "cat >> ~/friday-hooks.log" }
Trust for project config
Your global hooks file runs without prompting — it's your own machine.
A project file is different, because it can be committed and shared. The first time a project defines hooks, Friday asks once:
Project 'my-app' defines shell-command hooks in .agents/hooks/hooks.json. Trust and run them? [y/N]
Approve and Friday remembers, keyed to the project rather than the working directory, so --worktree runs of the same repo don't ask again. The approval covers that exact file: edit it and Friday asks again, telling you the config changed since you approved it. Decline — or run somewhere nothing can answer the prompt — and Friday skips that project's hooks for the run.
Approving project hooks runs whatever shell commands the file contains, on every matching event, for as long as the file stays unchanged. Read .agents/hooks/hooks.json before you approve it in a repo you don't control.
Limits
- Hooks only observe. A command's exit code and output never block, deny, or modify the thing it fired on.
- Each command gets 10 seconds. Friday ignores a command that times out or exits non-zero, and shows nothing in the UI. To confirm a hook runs, have it write to a file.
PreToolUseandSessionStartnever fire. Friday accepts both in config, so a typo won't break the file, but commands attached to them never run.
Hooks from plugins
An enabled plugin can ship hooks. Friday loads <plugin>/hooks/hooks.json, or the paths named by the hooks field in the plugin's manifest.
Enabling the plugin is the opt-in. A disabled plugin contributes no hooks, and an enabled plugin's hooks don't trigger the project trust prompt above — you already made that decision when you enabled it.
Read next
- Plugins & marketplaces: installing and enabling plugins.
- Permissions: what Friday asks approval for in the first place.
- File & directory layout: every path Friday reads and writes.