Agent hooks

st hooks install — a one-time setup that lets st see what Claude Code, Codex, Gemini CLI, opencode, Pi, Kimi Code and Qwen Code are doing.

st shares a terminal; it does not know what is running inside it. Hooks are how it finds out — whether an agent is working, waiting for permission, or done. That is what lets a phone say "this one is waiting for you".

st hooks install      # put them in, or bring them up to date
st hooks install gemini   # only the agents named: claude, codex, gemini, opencode, pi, kimi, qwen
st hooks status       # how things stand
st hooks uninstall    # take them out
st hooks auto off     # do not update them on your behalf

The apps can install them too: ⚠ Hooks on the machine's screen opens st and agent hooks with Install hooks, or Install on the bar that appears over a terminal where an agent runs without them.

Files it changes

st hooks install on its own does every agent it can hook that is on the machine; one that is not there is skipped. st hooks status also lists the agents it finds but has no hooks for yet (GitHub Copilot CLI, Aider, goose), with the reason.

ToolFileWhat goes in
Claude Code~/.claude/settings.json (or CLAUDE_CONFIG_DIR)st hook claude --hv 2 on each event, with a 10-second timeout
Codex~/.codex/hooks.json (or CODEX_HOME)st hook codex --hv 3 on each event, and its trust in ~/.codex/config.toml
~/.codex/config.tomlnotify = ["st", "hook", "codex"]
Gemini CLI (st 0.3.71 and later)~/.gemini/settings.jsonst hook gemini --hv 1 on each event, with a 10-second timeout
opencode (st 0.3.101 and later)~/.config/opencode/plugins/st-hook.jsa plugin that sends its turns as st hook opencode
Pi (st 0.3.109 and later)~/.pi/agent/extensions/st-hook.jsan extension that sends its turns as st hook pi
Kimi Code (st 0.3.101 and later)~/.kimi-code/config.toml[[hooks]] tables running st hook kimi --hv 1, with a 10-second timeout
Qwen Code (st 0.3.101 and later)~/.qwen/settings.jsonst hook qwen --hv 1 on each event, in Claude Code's shape

Fourteen Claude Code events are listened to: UserPromptSubmit, Stop, StopFailure, PermissionRequest, Notification, SubagentStart, SubagentStop, SessionStart, SessionEnd, TeammateIdle, TaskCreated, TaskCompleted, PreToolUse, PostToolUse. Gemini CLI's are seven: SessionStart, BeforeAgent, AfterAgent, Notification, SessionEnd, BeforeTool, AfterTool.

Settings entries

Put it in by hand if you would rather. This is all the command does.

Under hooks in ~/.claude/settings.json, the same shape for each of the fourteen events:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          { "type": "command", "command": "/opt/homebrew/bin/st hook claude --hv 2", "timeout": 10 }
        ]
      }
    ]
  }
}

The path in command is wherever st actually is: the st on your PATH when that is this same binary, and otherwise this executable's own path. A path with a space in it is quoted.

Gemini CLI's ~/.gemini/settings.json takes the same shape, with st hook gemini --hv 1 as the command and the timeout in milliseconds (10000).

Codex runs a hook from ~/.codex/hooks.json only once it is trusted, so st also writes each one's trust into ~/.codex/config.toml, as Codex's own /hooks approval would. That part is easier left to the command. The notify line goes in ~/.codex/config.toml as well, at the top level:

notify = ["/opt/homebrew/bin/st", "hook", "codex"]

A notify of Codex's own is left alone. There is only one notify program, so writing ours over it would take theirs away; it says Codex already has a notify program; left alone, and the hooks in hooks.json still go in. A machine without Codex is skipped too.

Anything it is about to change is copied first:

~/.claude/settings.json.st-backup-20260928-200347

Running it again is safe. When everything is already as it should be, that is all it says.

Versions, and keeping them current

The set st installs has a version, the --hv 2 at the end of Claude Code's command. It goes up whenever the events or the command change. Codex's set is version 3 (st 0.3.92 and later): its command is written so PowerShell can run it, since on Windows the version 2 command failed on every hook. st hooks status says where each agent stands:

StatusMeaning
installedthis st's version, every event
outdatedan older st wrote them; st hooks install updates them
partly installedthis version, with some events taken out since
not installedno st hooks

st hooks install updates in place: only st's own entries change — a second one for the same event and one for an event st no longer listens to are taken out — and the file is backed up first. Your own hooks stay where they are.

Once you have installed st's hooks on a machine, st keeps them current. When it starts — after an upgrade, that is the new version's first start — it brings an outdated set up to date the same way, with a backup, and says so in its log and in st ls for a day. A set with events you took out is left as you left it. To stop it:

st hooks auto off     # st hooks auto on to turn it back on

or turn off Keep agent hooks up to date in st and agent hooks (the machine screen's ⋯ menu) in the app. An st from before versions (v0.3.53 and earlier) runs these hooks fine, but does not recognise them as its own.

Data sent

The st hook those files run sends one short event to the st agent on the same machine, and stops there. The agent turns it into the states and prompts the apps show; a notification that carries one is encrypted to each phone's own key.

What it sends is a fixed list: the event name, the session id, a notification's type, title and message, the tool name, what a tool was asked to do (the command, file path or plan a permission is for), a subagent's id and kind, the short description an agent gives a subagent it starts (80 characters, never the task itself), where Claude Code keeps its agent teams, a task's subject, an error's kind, and from Codex its last reply.

Not the transcript and not the prompt. What a tool was asked to do is cut off at 4000 bytes, every other field at 500.

st hook never makes the agent wait, never prints anything, and always exits 0. PermissionRequest, PreToolUse and PostToolUse are watched and never answered: the decision stays in the terminal.

Timing

To sessions started after the install. A Claude Code already running keeps the settings it read when it started.

Taking it out

st hooks uninstall

Only the hooks st wrote, along with any group or event left empty by their going. Hooks you added yourself are left alone, and Codex's notify is removed only when it was st's.