Hooks
Hooks run a shell command when a rozi UI observes an event, such as a pane exiting or a session attaching. Use them for small, fire-and-forget reactions; use a service with rozi subscribe when automation needs state or retries.
Add a hook
Add a [[hooks]] entry to your config for each command:
[[hooks]]
event = "pane-exited"
run = "notify-send \"pane $ROZI_PANE exited with code $ROZI_CODE\""
[[hooks]]
event = "session-attached"
run = "~/.config/rozi/on-attach.sh"Each entry needs event, one of the event IDs below, and run, a nonempty command string. rozi runs run through command_shell with the event's fields in the environment.
- Several entries may use the same event. rozi launches them in config order.
- Hook commands run asynchronously and may overlap.
- At most 32 hook and detached
execjobs run at once. Further launches are skipped until a slot frees. - An unknown event ID or an empty command produces a config warning, and that entry is skipped.
- Reloading the config applies hook changes.
Events and fields
| Event | When it fires | Fields |
|---|---|---|
pane-spawned | A workspace pane is created. | pane, workspace, command, cwd |
pane-exited | A pane's process exits by itself. Closing a pane does not fire it; the close shows as layout-changed. | pane, code, focused |
pane-status-changed | A pane's reported status changes or clears. Seeding status on attach does not fire it. | pane, status, reason, previous_status, previous_reason, focused |
pane-title-changed | A pane's terminal title settles on a new value. A title that changes several times a second is coalesced, so only the value it comes to rest at fires. | pane, title, previous_title, focused |
pane-cwd-changed | A pane's working directory changes. | pane, cwd, previous_cwd, focused |
agent-state-changed | An agent's state changes, including when it appears in a pane and when it goes. Seeding agents on attach does not fire it. | pane, agent, label, ref, state, previous_state, reason, focused |
foreground-changed | The program running in a pane changes, including when the pane returns to its shell prompt. | pane, program, previous_program, pid, focused |
session-notification | A notification is sent to the session. Replaying the retained inbox does not raise it again. | id, level, title, message, session |
bell | A pane rings the terminal bell. Bells within 250 ms of the one reported are coalesced into it. | pane, focused |
focus-changed | Focus moves to another workspace pane. | pane |
workspace-switched | The active workspace changes. | workspace |
layout-changed | The attached session's layout reaches a new accepted revision. | revision, author |
session-attached | The client finishes attaching. | session, client_id, controller, read_only |
session-detached | The client intentionally leaves or switches sessions. | session |
session-renamed | The attached session is renamed. | session, previous |
session-created | A named session is created from an empty target. | session |
controller-changed | Layout control changes or is released. | controller, self_controller, reason |
client-joined | A client joins the attached session's roster. | client_id, client_name, count |
client-left | A client leaves the attached session's roster. | client_id, client_name, count |
profile-loaded | A profile seeds a newly created session. | profile, path, session |
profile-applied | A profile replaces the panes in an existing session. | profile, path, session |
profile-saved | A profile is saved or overwritten. | profile, path |
config-reloaded | A live config reload applies a usable document. It fires even with field-level warnings, but not for a rejected document. | path |
Every field is a string:
workspaceis one-based.commandandcwdare empty when the pane inherited them.focused,read_only, andself_controllerare"true"or"false".focusedsays whether this client had the pane focused, including when a pane exits and closing it moves focus away.controllerand the status fields are empty when there is no value.pane-title-changed.titleis the title the pane now reports, after display-text sanitization, and is empty when the program cleared it.previous_titleis what the session last reported and is empty when it had none. A title that keeps changing - a spinner, an elapsed clock - fires once when it holds still, not on every frame. A pane that exits reports its last title first.pane-cwd-changed.cwdis the pane's new working directory on the session host, andprevious_cwdis the one it left. Both are empty when there is no value. A shell integration reports the directory as the program runs, so a command that changes directory without shell integration fires only when rozi can otherwise observe it.agent-state-changeddescribes each agent asrozi agents listdoes.stateandprevious_stateareworking,blocked,idle,done, orerror;previous_stateis empty when the agent appears, andstateis empty when it goes.agentis the agent's ID, such asclaude, andlabelits name.reasonis why the agent is in its state, when it says.refis the agent's exact reference as JSON: pass it torozi agents get --reforagents wait --ref. An agent keeps itsrefacross every turn it works through and finishes. An agent replaced in the same pane, such as one relaunched, has a newref, so the replacement reads as one agent going and another appearing.foreground-changed.programis the executable name of the program the pane runs, such ascargo, and is empty when the pane is at its shell prompt. The pane's shell is the one it was started with, or a common shell such asbashorzsh, on the machine running the session.pidis that program's process group on the same machine, and is empty when nothing runs or the platform cannot read it. The same program restarted in a new process group fires again. rozi samples a pane's foreground, so a command that starts and finishes between two samples may not fire.layout-changed.authorisself,client, orserver.controller-changed.reasonisreleased,expired, orgranted.
rozi subscribe uses the same event names and fields, nested under data in each event object.
Environment
Hook commands inherit the client's environment and also receive:
| Variable | Value |
|---|---|
ROZI_EVENT | Event ID. Always present. |
ROZI_<FIELD> | One variable per event field, with the field name in uppercase: pane becomes ROZI_PANE. |
ROZI_BIN | Path to the running rozi executable, when available. |
ROZI_SOCKET | The UI's control endpoint, when control is available. |
ROZI_REMOTE_HOST | The resolved remote host, while this client is attached to a remote session. |
To call back into rozi from a hook, use ROZI_BIN and ROZI_SOCKET. Check both first, because a UI can run without a control endpoint:
[[hooks]]
event = "pane-exited"
run = '''
if [ -n "${ROZI_SOCKET:-}" ] && [ -n "${ROZI_BIN:-}" ]; then
"$ROZI_BIN" --socket "$ROZI_SOCKET" switch-workspace 1
fi
'''Lifecycle
Hooks run on the client machine, even when the session lives on a remote host.
rozi discards a hook's stdin, stdout, stderr, and exit status; redirect output in the command if you need it. rozi does not wait for, retry, or supervise hook processes, and a burst of events can skip hooks once 32 jobs are already running. For state, retries, or long-lived event handling, run rozi subscribe from a [[services]] entry instead.
Shared sessions
Hooks belong to UI clients, not to session servers. Each attached client loads its own hooks. Hooks for events the session raises run only on the client holding layout control, so one change runs one hook however many clients are attached: pane-spawned, pane-exited, pane-status-changed, pane-title-changed, pane-cwd-changed, agent-state-changed, foreground-changed, bell, layout-changed, client-joined, client-left, controller-changed, session-renamed, and session-notification. While a session has no controller, these hooks do not run. Hooks for every other event run on the client where it happened.
Automation that must hear session events with no UI attached subscribes to the session with rozi --session <NAME> subscribe.
No hooks run while every client is detached, and a client that crashes cannot run a final session-detached hook.