Skip to content

Hooks reference

For a guided introduction, start with How hooks work. The source is backend/src/hooks.rs.

Event Fires Working directory Prompts answered by
session-created While a new agent session in a git repo is set up. Global scripts first (they create the worktree), then repo scripts. The session waits. Global: project dir. Repo: the new worktree. The UI, the owning workflow, or nobody for scheduled runs
session-ready Once, when spwn first binds the session to the agent’s session id. Worktree The UI or the owning workflow
session-turn After each completed agent turn. Worktree The UI or the owning workflow
session-integrate When a session is verified: in a throwaway checkout of the session merged into its base, removed afterwards. The verify passes only if at least one script ran and every script exited 0. See Sync, verify, merge. The trial checkout, for both scopes The UI; nobody when the land queue or a workflow verifies
session-deleted When a session is deleted. Repo scripts first, then global (which remove the worktree). Repo: worktree. Global: project dir. The UI or the owning workflow
workflow-started When a workflow run starts. Output goes to the run’s log. Project dir Nobody
workflow-stopped When a workflow run ends. Project dir Nobody

Session events fire only for sessions with their own worktree branch. A session in a non-git folder, or one created while global hooks are off, gets none of them. The exception: a session with an environment (exec) but no worktree still gets its repo session-deleted scripts, so the environment can be torn down.

For session-integrate and the workflow events, the repo scope is read from the directory they run in: the trial checkout, or the project dir. For every other session event it is read from the session’s worktree.

Hooks inherit spwn’s own environment, plus these:

Variable Events Meaning
SPWN_EVENT All The event name.
SPWN_PROJECT_DIR All The project’s folder (your main checkout).
SPWN_WORKTREE All The session’s worktree. In global session-created, the path spwn proposes and the worktree script should create (it doesn’t exist yet). In session-integrate, the trial checkout. In workflow events, the project dir.
SPWN_TERMINAL_ID All session-* spwn’s id for the session. Stable for its life; good for naming containers, ports and databases.
SPWN_BRANCH All session-* The session’s branch, e.g. spwn/1a2b3c4d. In global session-created, the branch to create.
SPWN_BASE_BRANCH All session-*, when known The branch the session was cut from and merges back into. For a fork, the parent session’s branch.
SPWN_SESSION_ID session-ready, session-turn, session-deleted The agent’s own session id. Not set during session-created or session-integrate.
SPWN_TURN_UUID session-turn The id of the turn that just finished.
SPWN_EXEC Repo session-created, session-ready, session-turn, session-deleted The environment prefix a session-created hook reported with exec=, if any.
SPWN_START_POINT Global session-created Set for a fork from an earlier turn: the commit the new branch should start at.
SPWN_TRIAL_WORKTREE session-integrate The trial checkout holding the merged result (same as SPWN_WORKTREE).
SPWN_SESSION_WORKTREE session-integrate The session’s own worktree.
SPWN_WORKFLOW workflow-* The workflow’s name.
SPWN_WORKFLOW_RUN_ID workflow-* The run’s id.
SPWN_WORKFLOW_STATUS workflow-stopped ok, error or stopped.
SPWN_BIN All Absolute path of the running spwn binary, for "$SPWN_BIN" prompt and "$SPWN_BIN" checkpoint.
SPWN_PROMPT_SOCK All (macOS, Linux) The socket spwn prompt connects to. You don’t use it directly.

A hook reports a value by printing a line to stdout or stderr:

Terminal window
echo "::spwn:set:: key=value"

The line may have leading whitespace. The rest is split at the first =, and key and value are trimmed; the value may contain spaces. The last report of a key wins. These lines are removed from the captured output. Unknown keys are ignored.

Key Honored from Meaning Default
worktree Global session-created Absolute path of the worktree the hook created. Ignored if it doesn’t exist. SPWN_WORKTREE if it exists; otherwise the session has no worktree.
branch Global session-created The session’s branch. SPWN_BRANCH
base Global session-created The branch the session merges into. SPWN_BASE_BRANCH
exec session-created (either scope); any event re-run from the Hooks panel Command prefix for the session’s interactive processes: agent TUI, shells, browser editor. Split like shell words. Must allocate a tty. Without it, the next three keys are ignored. None: run on the host.
execHeadless As exec Prefix for scheduled runs. Must not allocate a tty. None: scheduled runs stay on the host.
execBin As exec Agent binary inside the environment. The agent definition’s binary name, resolved on the environment’s PATH.
execShell As exec Shell for shell panes inside the environment. /bin/sh

If both scopes of session-created report exec, the repo scope’s environment replaces the global one. Details: Talking back to spwn.

spwn prompt [--multi] [--header TEXT | --header=TEXT] "Question?" [option ...]

Asks the user a multiple-choice question from inside a hook and prints the answer.

  • No options: a Yes / No confirm.
  • --multi: the user may choose several; they’re printed on one line, joined by , .
  • --header: a short tag shown with the question.
Exit code Meaning
0 Answered. The chosen label(s) on stdout.
2 Declined: timed out after 5 minutes in the UI, no workflow handler, or nobody to ask (a scheduled run’s session-created, workflow-* events, verifies started by the land queue or a workflow).
3 Usage error, not inside a spwn hook (SPWN_PROMPT_SOCK unset), or spwn unreachable.

macOS and Linux only. See Asking the human.

spwn checkpoint <turn-uuid>

Snapshots the worktree as a checkpoint for the Timeline, keyed to the session and the turn. Reads SPWN_SESSION_ID and SPWN_WORKTREE from the environment. spwn’s default session-turn.d/20-checkpoint.sh calls it after every turn:

Terminal window
"$SPWN_BIN" checkpoint "$SPWN_TURN_UUID"
Exit code Meaning
0 Snapshot taken, or nothing to do (SPWN_SESSION_ID or SPWN_WORKTREE unset).
1 The snapshot failed, or spwn’s data folder couldn’t be found.
2 No turn id given.
~/.spwn/hooks/ global: every session, every project
<event>.sh
<event>.d/
10-first.sh
20-second.sh
<repo>/.spwn/hooks/ repo: committed with the code
<event>.sh
<event>.d/
…
  • Scopes: global (~/.spwn/hooks) runs before repo (.spwn/hooks), except session-deleted, where repo runs first.
  • Within a scope: <event>.sh first, then each file in <event>.d/ sorted by filename.
  • Which files run: <event>.sh always. In <event>.d/, regular files that are executable or end in .sh; hidden files and anything else are skipped.
  • How they run: executable files directly (shebang honored); others as sh <file>.
  • Sequential and synchronous: one script at a time, and the session waits. No timeout.
  • stdin is /dev/null.
  • Output: stdout and stderr stream live to the session’s Hooks panel; the last 8 KB per script is kept.
  • Failure: a non-zero exit shows a red dot and a toast. It doesn’t stop later scripts or fail the session.
  • Opt-in: no files, no hooks.
  • Live: scripts are read from disk each time an event fires.
  • Global on/off: Settings → Hooks → Run shared global hooks. Off skips the entire global folder, including spwn’s worktree creation, so new sessions get no worktree and no session hooks.

Installed into ~/.spwn/hooks on startup, from backend/assets/hooks:

File Does
session-created.d/10-worktree.sh Creates the worktree and branch, enables rerere if unset, clones heavy gitignored folders in, reports worktree/branch/base.
session-turn.d/10-commit.sh Commits the turn onto the session branch.
session-turn.d/20-checkpoint.sh spwn checkpoint for the turn.
session-deleted.d/90-worktree.sh Removes the worktree, then deletes the branch.

A .spwn-version file in ~/.spwn/hooks records which version installed them. On a fresh install spwn writes all four. When the version changes it overwrites the ones that still exist and leaves deleted ones deleted. Files you add are never touched.