Hooks reference
For a guided introduction, start with How hooks work. The source is
backend/src/hooks.rs.
Events
Section titled “Events”| 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.
Environment variables
Section titled “Environment variables”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. |
::spwn:set:: keys
Section titled “::spwn:set:: keys”A hook reports a value by printing a line to stdout or stderr:
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
Section titled “spwn prompt”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
Section titled “spwn checkpoint”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:
"$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. |
File layout and ordering
Section titled “File layout and ordering”~/.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), exceptsession-deleted, where repo runs first. - Within a scope:
<event>.shfirst, then each file in<event>.d/sorted by filename. - Which files run:
<event>.shalways. 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.
Default global hooks
Section titled “Default global 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.