Skip to content
Gaurav Gosain edited this page Oct 4, 2026 · 3 revisions

Run shell commands when windows, workspaces, sessions and agents change.

A hook is a shell command TUIOS runs when something happens: a window opens, focus moves, you switch workspace, you attach or detach, an agent finishes. Hooks live in the [hooks] table of your config file.

[hooks]
after-new-window = "notify-send 'TUIOS' \"opened $TUIOS_WINDOW_NAME\""
after-workspace-switch = "echo \"$TUIOS_PREV_WORKSPACE -> $TUIOS_WORKSPACE\" >> ~/.tuios-ws.log"

Warning

Event names use hyphens

Other config keys use snake_case. Hook event names use hyphens and are case-sensitive: after-new-window, not after_new_window. An unknown name is not an error. TUIOS logs one line and the hook never runs. tuios list-hooks shows what was actually loaded.

Events

Event Fires when Runs in
after-new-window A window is created daemon
after-close-window A window closes, by keybinding or because its process exited daemon
after-focus-change Focus moves to a different window daemon
after-workspace-switch The session switches to a different workspace daemon
after-agent-state An agent in a pane changes state, filtered by the [notifications.agent] policy daemon
after-command-finished A pane's shell reports, through its OSC 133 marks, that a command finished daemon
after-attach A client attaches to a session, including a switch between sessions client
after-detach A client detaches from a session client
after-layout-change The tiling layout changes client
after-resize A window is resized, by keyboard or by dragging its border client

A hook runs on the side that owns the fact it reports:

  • Daemon events describe the session. The daemon runs them, so they fire when nobody is attached, and they fire once however many clients are attached.
  • Client events describe one client. Each attached client runs them from its own config. Three clients attaching is three after-attach firings.
  • A standalone TUIOS (tuios --standalone) has no daemon and runs every hook itself.

after-agent-state follows the same [notifications.agent] settings as the other agent alerts, including the settle delay, so it does not fire on every brief change. suppress_focused only applies while a client is attached. A command in [notifications.agent].command is registered as one more after-agent-state hook. See Notifications.

One command or several

A value is one string, or an array of strings:

[hooks]
after-attach = "my-greeting"

after-new-window = [
  "logger -t tuios \"new window $TUIOS_WINDOW_ID\"",
  "touch ~/.cache/tuios/last-window",
]

The commands in an array run at the same time, not in order. If order matters, put the steps in one string joined with &&.

What a hook receives

TUIOS runs the string with sh -c. There are no arguments and no stdin. Everything arrives as environment variables.

Every variable is set for every event. A value that does not apply is empty for text and 0 for numbers, so a script can read any of them without checking first.

Variable Meaning
TUIOS_EVENT The event name
TUIOS_WINDOW_ID Window ID
TUIOS_WINDOW_NAME Window name or title
TUIOS_WORKSPACE Workspace number
TUIOS_SESSION_ID Session name. Empty in a standalone TUIOS
TUIOS_PREV_WORKSPACE The workspace you came from
TUIOS_LAYOUT bsp, master-stack, scrolling or floating
TUIOS_WIDTH, TUIOS_HEIGHT The window's new size in cells
TUIOS_AGENT_STATE The agent's new state
TUIOS_AGENT_PREV_STATE The state it left
TUIOS_AGENT_HARNESS The detected harness, for example claude-code
TUIOS_AGENT_MESSAGE The last message the agent reported
TUIOS_COMMAND For after-command-finished, the command line, cut to 512 bytes with likely secrets masked
TUIOS_EXIT_CODE For after-command-finished, the exit status. Empty when the shell sent none
TUIOS_DURATION_MS For after-command-finished, how long the command ran

Which variables carry data for each event:

Event Variables
after-new-window, after-close-window, after-focus-change TUIOS_WINDOW_ID, TUIOS_WINDOW_NAME, TUIOS_WORKSPACE, TUIOS_SESSION_ID
after-workspace-switch TUIOS_WORKSPACE, TUIOS_PREV_WORKSPACE, TUIOS_SESSION_ID
after-attach, after-detach TUIOS_WORKSPACE, TUIOS_SESSION_ID
after-layout-change TUIOS_LAYOUT, TUIOS_WORKSPACE, TUIOS_SESSION_ID
after-resize TUIOS_WINDOW_ID, TUIOS_WINDOW_NAME, TUIOS_WIDTH, TUIOS_HEIGHT, TUIOS_WORKSPACE, TUIOS_SESSION_ID
after-agent-state TUIOS_WINDOW_ID, TUIOS_WINDOW_NAME, TUIOS_AGENT_STATE, TUIOS_AGENT_PREV_STATE, TUIOS_AGENT_HARNESS, TUIOS_AGENT_MESSAGE, TUIOS_SESSION_ID
after-command-finished TUIOS_WINDOW_ID, TUIOS_COMMAND, TUIOS_EXIT_CODE, TUIOS_DURATION_MS, TUIOS_SESSION_ID

A hook the daemon runs gets the daemon's environment, not your current shell's. A daemon started by tuios new keeps the environment of the shell that started it. Use full paths when in doubt.

When a hook does not fire

tuios list-hooks

This lists every registered command with the side that runs it, how many times it ran, its last exit code, when it last ran and its last error. The error is the tail of the command's stderr.

  • No row: the hook was never loaded. Check the event name.
  • 0 runs: the command is registered and the event has not happened.
  • Non-zero exit: the command ran and failed. The error says why.

tuios list-hooks --event after-close-window shows one event, and --json gives machine-readable output. Client hooks are listed only while a client is attached.

The daemon also logs a warning for every failing hook. Run tuios daemon --log-level=basic to log one line per firing as well.

Limits

  • Hooks load at startup. Editing [hooks] does not reload them. Restart the client for client hooks, and run tuios kill-server so the daemon restarts for daemon hooks. This stops every session and the processes in them. The next tuios restores the saved layouts, but not the running programs.
  • No timeout. A hook that never exits keeps running. On detach, and when the daemon shuts down, TUIOS waits at most 2 seconds for running hooks.
  • Stdout is discarded. Only the tail of stderr (1 KiB) and the exit code are kept, for list-hooks. Redirect to a file if you need the output.
  • No ordering between hooks. Each command runs in its own goroutine, so do not rely on the order of two events that happen together.
  • Restored windows count as new. When the daemon restores a saved session, after-new-window fires for each window it brings back.
  • after-detach needs a daemon session. A standalone TUIOS has nothing to detach from.
  • Wrong types are ignored. A number or a boolean where a string or array belongs is dropped without a message.

Examples

Log every event, to see what fires and when:

[hooks]
after-new-window = "echo \"$TUIOS_EVENT $TUIOS_WINDOW_ID $TUIOS_WINDOW_NAME\" >> ~/.tuios-hooks.log"
after-close-window = "echo \"$TUIOS_EVENT $TUIOS_WINDOW_ID\" >> ~/.tuios-hooks.log"
after-focus-change = "echo \"$TUIOS_EVENT $TUIOS_WINDOW_NAME\" >> ~/.tuios-hooks.log"
after-workspace-switch = "echo \"$TUIOS_EVENT $TUIOS_PREV_WORKSPACE->$TUIOS_WORKSPACE\" >> ~/.tuios-hooks.log"
after-layout-change = "echo \"$TUIOS_EVENT $TUIOS_LAYOUT\" >> ~/.tuios-hooks.log"
after-resize = "echo \"$TUIOS_EVENT ${TUIOS_WIDTH}x${TUIOS_HEIGHT}\" >> ~/.tuios-hooks.log"

Show attach state in a status bar:

[hooks]
after-attach = "echo attached > ~/.cache/tuios-state"
after-detach = "echo detached > ~/.cache/tuios-state"

Send a desktop notification when an agent needs you:

[hooks]
after-agent-state = "[ \"$TUIOS_AGENT_STATE\" = needs_input ] && notify-send \"$TUIOS_WINDOW_NAME needs input\""

To reach your phone without a script, use the [notify] table. It sends Inbox items through ntfy, Pushover or a webhook, with a link to the item on tuios-web. See Push notifications.

With [agents] enabled = false, agent hooks such as after-agent-state do not run. See Turn off agent features.

Related

Clone this wiki locally