Beta

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

EventFires when
UserPromptSubmitA turn starts and the agent begins working on your message.
PostToolUseA tool call finishes, whether it succeeded or failed.
StopThe agent finishes responding to a turn.
PermissionRequestFriday is about to ask you to approve something.
PermissionResolvedAn approval prompt resolved — you approved it, you rejected it, or it errored.
SessionEndFriday 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

ScopePath
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:

FieldRequiredWhat it does
matcherNoNarrows 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.
hooksYesThe commands to run when the group matches.

Each command entry takes two more:

FieldRequiredWhat it does
typeNo"command", or leave it out. Friday skips entries of any other type.
commandYesThe 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.

Warning

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.
  • PreToolUse and SessionStart never 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