Agent hooks
The built-in
hooks
extension wires each coding agent's lifecycle hooks to
thurbox-cli session signal
so every session reports its state back to Thurbox. The session list then shows, at a
glance, which agents are
working
,
blocked
,
done
, or
idle
— a colored dot per row, rolled up per repo group.
On by default. Unlike other extensions , hooks ships embedded in the binary and is auto-activated on first run — the default agent's hook is pre-configured with zero setup. It installs through the ordinary extension machinery (it just lives inside Thurbox instead of being fetched).
Overview #
The hook command is always
thurbox-cli session signal --state <working|blocked|done|idle>
, suffixed
|| true
so it can never break the agent. Identity comes from the injected
$THURBOX_SESSION
environment variable, so a hook passes
no ids
. The reported state is persisted on the session row and picked up by the TUI within a
tick (via
PRAGMA data_version
), so a hook firing headlessly is honored too.
The five states #
| State | Colour | Glyph | Meaning |
|---|---|---|---|
Working |
yellow | animated braille spinner (⠋⠙⠹…) |
the agent is actively running |
Blocked |
red | ◆ |
the agent needs input or approval |
Done |
blue | ● |
a turn just finished; shown until you switch away |
Idle |
green | ○ |
acknowledged, never active, or at rest |
Error |
red | ✗ |
reserved for a crashed agent (not derived yet) |
A
Done
session becomes
Idle
once you move focus off it (you've acknowledged it); a
Working
session that goes quiet for 10 s is treated as
Idle
so an interrupted turn never spins forever. Repo groups roll up to their most-urgent
member (
Blocked > Error > Working > Done > Idle
), and the status colours are tunable theme keys.
Per-agent coverage #
How much each agent can report depends on the lifecycle surface its CLI exposes — Thurbox maps whatever hooks an agent offers, and the dot is accurate for the edges that agent can express:
| Agent | idle | working | blocked | done |
|---|---|---|---|---|
| claude | ✓ | ✓ | ✓ | ✓ |
| opencode | ✓ | ✓ | ✓ | ✓ |
| codex * | ✓ | ✓ | — | ✓ |
| antigravity | ✓ | ✓ | ✓ | ✓ |
| aider | — | — | ✓ | — |
| copilot * | ✓ | ✓ | ✓ | ✓ |
| vibe * | — | ✓ | — | ✓ |
| pi * | ✓ | ✓ | ✓ | ✓ |
| omp * | ✓ | ✓ | ✓ | ✓ |
*
Experimental
— the hook schema for codex, copilot, vibe, pi, and omp is newer or not
exhaustively verified against the live CLI. If event names differ, the fix is a one-file edit to the
extension payload (no code change), and an unrecognized hook is a fail-open no-op
(
|| true
) so the agent is never broken.
How each agent is wired #
-
claude
— a managed settings file passed via
--settings(claude merges it with your own settings, never clobbering them).SessionStart→ idle,UserPromptSubmit/PreToolUse→ working,Stop→ done, andNotification→ blocked only for permission/approval prompts (an idle nudge is ignored so a session never flips to a false red). -
opencode
— a plugin dropped into
~/.config/opencode/plugin/(only when opencode is installed).session.created→ idle,chat.message→ working,permission.asked→ blocked,session.idle→ done. -
codex
(experimental)
— codex's claude-shaped
hooks.jsonis JSON-merged into~/.codex/hooks.json(reversibly; uninstall prunes exactly our entries).SessionStart→ idle,UserPromptSubmit/PreToolUse→ working,Stop→ done. No blocked — codex's top-level hooks have no permission event. This replaced the oldernotifyoverride (which reported done only). -
antigravity
— the
agyCLI loads hooks from its shared~/.gemini/settings.json, so Thurbox JSON-merges its entries in without clobbering your settings.agyadopted claude's hook schema (verified against agy 1.0.9), so the mapping mirrors claude:SessionStart→ idle,PreToolUse→ working,Stop→ done, andNotification→ blocked (permission/approval prompts only). It has noUserPromptSubmit, so working is signaled at the first tool call. -
aider
—
--notifications-commandreports the only edge aider exposes: blocked (waiting for input). -
vibe
(experimental)
— Mistral Vibe loads hooks from
~/.vibe/hooks.toml; Thurbox drops a managed file in (only when vibe is installed). Verified against vibe 2.21.0:pre_tool→ working,post_agent→ done. No blocked — vibe's only hook types arepre_tool/post_tool/post_agent(no permission event), so a tool awaiting approval reads as working. If you already maintain your ownhooks.tomlthe write is refused (never clobbered) — vibe simply goes unreported. -
copilot
(experimental)
— GitHub Copilot CLI loads hooks from its own
~/.copilot/hooks/dir, so Thurbox drops a managed standalone file in (only when copilot is installed), never touching your other hook files.sessionStart→ idle,userPromptSubmitted/preToolUse→ working,agentStop→ done, andnotificationmatched topermission_prompt→ blocked. Bothbashandpowershellcommands ship, so status works on Windows too. -
pi
(experimental)
— the pi.dev CLI auto-discovers TypeScript extensions from
~/.pi/agent/extensions/, so Thurbox drops a managed one in (only when pi is installed).session_start→ idle,agent_start/tool_execution_start→ working,agent_end→ done, and a structuredask_user_questiontool call → blocked (pi has no permission hook, so a turn that ends by asking in prose signals done, not blocked). -
omp
(experimental)
— Oh My Pi is Pi-compatible and likewise auto-discovers TypeScript extensions,
from
~/.omp/agent/extensions/, so Thurbox drops a managed one in (only when omp is installed). It mirrors pi's extension but maps OMP's structured user-question tool — namedask— to blocked (it recognizes bothaskand pi'sask_user_question). Verified against OMP 17.0.6.
These use three manifest capabilities —
[[agent_patches]]
(append args to an agent),
[[external_files]]
(place a file in an agent's own config dir), and
[[config_merges]]
(reversibly deep-merge JSON into an agent's shared config). Each is fully reversible:
uninstall removes exactly what was added, matched by the
session signal
marker.
Where the config lives #
The wiring is applied
only to agents Thurbox launches
— it never edits your own global agent config (e.g. your personal
~/.claude/settings.json
). For claude the managed hooks file is passed with
--settings
, which claude
merges on top of
your own settings: inside a Thurbox session both your hooks and Thurbox's fire, while a
plain
claude
outside Thurbox sees only your own. The other agents are wired by a reversible merge
into — or a managed file dropped in — their own config dir.
| Agent | On-disk location | How it's applied |
|---|---|---|
| claude | ~/.config/thurbox/hooks/claude.json |
--settings
flag on the
claude
agent (claude merges it)
|
| aider | — (no file) |
--notifications-command
flag on the
aider
agent
|
| opencode | ~/.config/opencode/plugin/thurbox-status.js |
managed plugin file |
| codex | ~/.codex/hooks.json |
reversible JSON-merge of our entries |
| vibe | ~/.vibe/hooks.toml |
managed file (refused if you already have one) |
| copilot | ~/.copilot/hooks/thurbox-status.json |
managed standalone file |
| antigravity | ~/.gemini/settings.json |
reversible JSON-merge of our entries |
| pi | ~/.pi/agent/extensions/thurbox-status.ts |
managed extension file (refused if you already have one) |
| omp | ~/.omp/agent/extensions/thurbox-status.ts |
managed extension file (refused if you already have one) |
The home dir is
~/.config/thurbox/hooks
for a release build and
~/.config/thurbox-dev/hooks
for a dev build, so the two stay isolated.
Inspect or customize.
To see exactly what Thurbox installed, read the file for the agent above (e.g.
cat ~/.config/thurbox/hooks/claude.json
). The injected
--settings
/
--notifications-command
flags themselves live in the
claude
/
aider
entries of
~/.config/thurbox/agents.toml
. You can hand-edit a managed file, but self-heal rewrites it from the embedded payload
on the next TUI start / heartbeat tick — so to keep a change, either deactivate
the extension and wire the hook yourself, or edit the payload source under
extensions/hooks/
and reinstall.
Opt out #
Hooks is the one extension that's on without you asking. Turn it off (and back on):
thurbox-cli extension deactivate hooks # remove the wiring; won't come back
thurbox-cli extension activate hooks # re-enable itDeactivating records an opt-out flag, so startup self-heal won't resurrect it. All the per-agent wiring is reversible, so opting out leaves your agent configs exactly as they were.
Files #
See
extensions/hooks
for the
README.md
, the
extension.toml
manifest, and the per-agent payloads (
claude.json
,
codex-hooks.json
,
antigravity-hooks.json
,
opencode-status.js
,
vibe-hooks.toml
,
copilot-hooks.json
).