Lifecycle hooks
Hooks are your own shell scripts that Plexus runs at key moments in a session's life, so every new worktree starts ready to work.
The three events
A hook is attached to one lifecycle event. Each hook has a name and a multi-line command, run through sh -c on macOS and Linux or cmd.exe /C on Windows — write it as a small script, not just a single line.
| Event | When it fires | Typical use |
|---|---|---|
| Create | A new session worktree is created — on a new branch or an existing one alike | Install dependencies, scaffold local config, warm caches |
| Run | The agent starts or resumes | Start a dev server, tail a log, kick off a watcher |
| Teardown | A session is torn down | Stop background processes, clean up scratch resources |
Hooks run on your machine with your permissions, your full PATH, and your toolchain. A repository's committed hooks run in addition to the ones you set up in Plexus, so adding a repo as a project is enough for its scripts to run — leave Run scripts committed to the repository on only for repos you trust. Plexus strips Anthropic credentials (ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN) from a hook's environment, but everything else is reachable.
Where hooks are defined
Hooks live at three levels, and lower levels inherit from higher ones:
- Global — apply to every workspace, project, and session.
- Workspace — apply to every project in that workspace.
- Project — apply to every session in that project. This level has two sources: the hooks you set up in Plexus, and the ones the repository commits to
.plexus/hooks.json. Both run — see Repo-committed hooks.
Sessions never define their own hooks; they always inherit the composed plan from their project. To change what a session runs, edit its project-level hooks. Open Settings → Worktrees & Hooks, pick a scope, then pick the Create / Run / Teardown event tab to edit that combination.
Composition modes
A non-global level doesn't just add its hooks to the inherited ones — it declares a combine mode that says how its hooks join what it inherits.
| Mode | Effect |
|---|---|
| Override | Discard all inherited hooks; only this level's hooks run (the managed built-ins still run — see below). |
| Before | Run this level's hooks ahead of the inherited ones. |
| After (default) | Run this level's hooks behind the inherited ones. |
| Parallel | Run this level's hooks concurrently, alongside the rest. |
Set the mode from the Combine mode dropdown on each event tab at the workspace or project scope.
Resolution and execution order
Plexus resolves the plan top-down — global → workspace → project — applying each level's combine mode. At the project level, the repository's committed hooks and your app-managed project hooks are first joined into one ordered list (the repository's first, by default); that combined list is what the project's combine mode then applies. The resulting plan then runs in four phases, always in this order:
- Before — sequential. A non-zero exit can abort the launch (see below).
- Action — the event's managed built-ins (for Create, the copy-paths step, and the port-file step when you've asked for one).
- Parallel — concurrent, best-effort. A failure here never aborts.
- After — sequential, and always runs.
Within the Hooks defined here list, use the up/down controls to set the order of your own hooks at that level. The live Resolved plan preview shows the final four-phase plan exactly as it will run — every hook, its source badge (Global / Workspace / Project / Built-in / From repo), and whether it's enabled. This is the fastest way to confirm inheritance did what you expect. Toggle any hook off without deleting it; disabled hooks stay in the list and appear dimmed in the preview.

The managed built-ins
On the Create event, Plexus runs up to two managed steps of its own. They live in the Action phase and are immune to Override: even a project that overrides every inherited hook still gets them.
- copy-paths copies configured files into the new worktree — this is how gitignored files like
.envreach the session. Configure it with Copy files into new session folders and Files and folders to copy (default.env,.env.local) — see Worktrees for the full path rules. - port-env-file writes the session's dev server ports into a file in the worktree. It only appears in the plan once Write ports to file names one; the setting is empty by default, so most plans never show this step. Unlike copy-paths it also runs again whenever the session is resumed, so the file always carries the ports the session is actually holding.
What a hook gets in its environment
Hooks inherit the environment Plexus itself was launched with — your PATH, your toolchain, your shell's variables — minus the Anthropic credentials, which are always stripped. On top of that, a hook running for a session with a reserved port block gets:
| Variable | Value |
|---|---|
PLEXUS_PORT | The session's first dev server port |
PLEXUS_PORT_BASE | The same number, for scripts that do arithmetic on it |
PLEXUS_PORT_COUNT | How many ports the block holds (10) |
PLEXUS_PORT_1 … PLEXUS_PORT_9 | The rest of the block |
PORT | The first port again — this one is configurable, via Extra port variables |
The same set reaches the agent and the session's terminal tab, so a server a hook starts and a command the agent runs by hand agree about which port to use.
pnpm dev --port $PLEXUS_PORT
Treat these as optional. They are absent for a session that holds no reservation — Assign dev server ports switched off, or a range with nothing free left in it — and a hook that hard-fails on an empty $PLEXUS_PORT would then stop a session from starting. See Dev server ports for what is and isn't guaranteed.
Repo-committed hooks
A team can share hooks by committing them to the repo at .plexus/hooks.json. The file maps each event to an ordered array of { name, command } entries; command may be multi-line and may call a repo script.
{
"create": [
{ "name": "Install deps", "command": "pnpm install" },
{ "name": "Setup", "command": "set -e\nbash .plexus/hooks/setup.sh" }
],
"run": [],
"teardown": []
}
When this manifest is present and Run scripts committed to the repository (default on) is on, its hooks run alongside the project hooks you set up in Plexus. Cloning the repo is enough for a teammate to get the team's setup, and it no longer costs them the hooks they added for themselves. How repository hooks combine with yours (Settings → Worktrees & Hooks) decides which set goes first:
| Value | Effect |
|---|---|
| Repository hooks first, then your app-managed hooks (default) | The committed hooks run, then yours. |
| Your app-managed hooks first, then the repository's | Your hooks run first, then the committed ones. |
| Repository hooks only (skip this project's app-managed hooks) | The committed hooks replace this project's app-managed hooks. |
Both sets belong to the project level, so they share that level's combine mode and run in the same phase. Your global and workspace hooks still run — unless this project's Combine mode is Override, which discards inherited hooks whichever value you pick. The order has no effect under Parallel either, where the project level's hooks run concurrently.
Like the other hook settings, it layers Global → Workspace → Project: one global choice covers every project, and a single project can still differ.
Plexus shows a "Repo hooks active" banner with the Run scripts committed to the repository toggle on the Project scope; turn it off to run only your own hooks, without deleting the file. If the manifest fails to parse, the error is surfaced in the editor and only your app-managed project hooks run, rather than breaking the session.
If you used repository hooks before this version: a committed .plexus/hooks.json used to replace that project's app-managed hooks — every event, even the ones the file says nothing about. Both sets run now, so hooks you set up in Plexus for such a project start running again the next time a session starts, resumes, or is torn down — including the teardown that runs when you delete a session or project, which happens without a session start. Open Settings → Worktrees & Hooks on the project to see exactly what will run (the editor points this out once, on projects that have both), and pick Repository hooks only if you want the old behavior back.
Repository hooks only is the one value that silences your own project hooks, and it follows the file. If a later commit empties .plexus/hooks.json — the file is still there, but declares nothing for an event — this project contributes nothing of its own to that event. The hooks you set up in Plexus don't come back to fill the gap.
Aborting on failure
Stop if a setup step fails (Settings → Worktrees & Hooks, default on) stops a session launch if a Create hook in the before or action phase exits non-zero — so you never get a half-set-up worktree. Run and teardown hooks never abort the lifecycle. Override the setting to make Create hooks non-blocking. Repository hooks and the ones you set up in Plexus are treated identically here — a non-zero exit from either aborts a Create the same way.
Hook timeout
Hook timeout (Settings → Worktrees & Hooks, default 600 seconds, set globally or per project) caps how long any single hook command may run. On expiry the command and everything it started are stopped, and the hook is recorded as timed out in the Hook output tab.
A timeout is deliberately not treated as the hook failing: it never stops a launch, even with Stop if a setup step fails on. Stopping there would delete the session's working copy — and, when Plexus cut it, its branch — over something transient, and a cold pnpm install or a first checkout of a large repository really can outrun ten minutes. So the session starts, whatever the hook managed to do is left in place, and the tab tells you which step was cut short. Rerun it yourself, or raise the timeout and start again. A hook that exits with an error still stops the launch as before — that's the hook saying no, rather than Plexus giving up waiting. Either way, a session started on an existing branch loses only its half-built worktree: the branch itself is never removed by an abort.
Raise the timeout if you have a legitimately long setup step. There is no "unlimited" option on purpose: a hook that leaves a background process running would otherwise hold the session's launch open indefinitely. If a hook is simply taking too long right now, Cancel on the starting session stops it.
Viewing hook output
Open a session's Hook output tab — on the right of the session tab row, set apart from Agent, Review, Explorer, and Git, with the session's Open localhost button beside it when it holds a block of dev server ports — to see what its hooks actually did. Each Create and Run hook is listed with its captured stdout, stderr, and exit status; a hook that exited non-zero (or that aborted the launch) is flagged in red, and a hook that printed nothing shows (no output). This is where a hook that prints something meaningful surfaces it, and the first place to look when a launch fails.
Plexus keeps the latest run of each event: Run hooks re-run on every start and resume, so the tab always reflects the most recent launch; Create hooks run once when the worktree is made, and their output is kept even if the create hook fails and the launch rolls back — so you can reopen a failed session and see exactly why. The output persists across app restarts.
Hook output is stored locally in Plexus's database so it survives restarts. If a hook prints secrets (for example running env or echoing a token), that text is written to disk. Each stream is capped at 64 KB.