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 behalfThe 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.
| Tool | File | What 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.toml | notify = ["st", "hook", "codex"] | |
| Gemini CLI (st 0.3.71 and later) | ~/.gemini/settings.json | st hook gemini --hv 1 on each event, with a 10-second timeout |
| opencode (st 0.3.101 and later) | ~/.config/opencode/plugins/st-hook.js | a plugin that sends its turns as st hook opencode |
| Pi (st 0.3.109 and later) | ~/.pi/agent/extensions/st-hook.js | an 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.json | st 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-200347Running 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:
| Status | Meaning |
|---|---|
| installed | this st's version, every event |
| outdated | an older st wrote them; st hooks install updates them |
| partly installed | this version, with some events taken out since |
| not installed | no 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 onor 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 uninstallOnly 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.