---
title: "Hooks"
description: "Two moments you can customize. How your services get arranged when you start them, and what happens when you create a worktree, like opening an editor or coding agent."
---
# Hooks

Hooks customize two things: how `slice up` presents your processes, and what happens when you create a new worktree. They're defined in the TOML output of your `thinslice.new-state`.

## Two kinds of hooks

### hooks.up

Control how `slice up` launches and arranges your processes.

Without a hook, `slice up` starts all processes as background daemons. With a hook, it arranges them in tmux according to your layout.

```
slice up                  # background all processes
slice up dev              # use hooks.up.dev — arrange in tmux
slice up full             # use hooks.up.full — different layout
```

Defined in your template output:

```toml
[hooks.up.dev]
tmux = """
new-window -n $BRANCH
split-v 70
send-top "$SHELL"
send-bottom "slice logs --follow"
focus-top
"""

[hooks.up.full]
tmux = """
new-window -n $BRANCH
split-h
split-right-v
send-left "cargo run --bin api -- --port $API_PORT"
send-topright "npm run dev -- --port $FRONTEND_HTTP_PORT"
send-bottomright "psql -p $PG_PORT"
focus-left
"""
```

### hooks.new-worktree

Run when creating a worktree with `slice local`, `slice tmp`, or `slice pr checkout`. These set up your interactive working environment — typically a tmux window with a coding agent or editor on one side and `slice up` on the other.

The hook name is a positional argument after the worktree name and flags. Everything after the hook name is passed through as arguments.

```
slice tmp fix-login --from current claude -p "fix the redirect"
#                                  ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
#                                  hook   passed as $@ to the hook
```

Defined in your template output:

```toml
[hooks.new-worktree.claude]
tmux = """
new-window -n $BRANCH
split-h
send-left "cd $SLICE_CODE_DIR && claude $@"
send-right "cd $SLICE_CODE_DIR && slice up"
focus-prev
"""

[hooks.new-worktree.psql]
tmux = """
new-window -n $BRANCH
split-h
send-left "cd $SLICE_CODE_DIR && psql -p $PG_PORT $@"
send-right "cd $SLICE_CODE_DIR && slice up"
focus-prev
"""

[hooks.new-worktree.vim]
tmux = """
new-window -n $BRANCH
split-h 70
send-left "cd $SLICE_CODE_DIR && vim $@"
send-right "cd $SLICE_CODE_DIR && slice up dev"
focus-left
"""
```

Note that `hooks.new-worktree` hooks are responsible for calling `slice up` themselves. This gives you control over whether processes start in the background or with a tmux layout, and which pane they run in.

## Hook types

A hook can use the tmux DSL, exec a script, or both.

### tmux

Define a tmux layout using the thinslice [tmux DSL](tmux-dsl.md). This is the common case for developers working in tmux.

```toml
[hooks.new-worktree.claude]
tmux = """
new-window -n $BRANCH
split-h
send-left "cd $SLICE_CODE_DIR && claude $@"
send-right "cd $SLICE_CODE_DIR && slice up"
focus-prev
"""
```

### exec

Run an arbitrary script. Use this when you're not in tmux, or when you need to do something the tmux DSL doesn't cover.

```toml
[hooks.new-worktree.zellij]
exec = "$SLICE_PROJECT_DIR/scripts/start-zellij.sh $SLICE_CODE_DIR $SLICE_STATE_DIR $@"
```

The script receives the same environment variables as your template: `$SLICE_CODE_DIR`, `$SLICE_STATE_DIR`, `$SLICE_PROJECT_DIR`, `$BRANCH`, plus all variables from the `[env]` section of `thinslice.toml`. `$@` and `$1`, `$2`, etc. come from the arguments passed after the hook name.

### Both

If both `tmux` and `exec` are present, the tmux DSL runs first, then exec. This is useful for setting up terminal panes and also launching a GUI application.

```toml
[hooks.new-worktree.cursor]
tmux = """
new-window -n $BRANCH
send "cd $SLICE_CODE_DIR && slice up"
focus-prev
"""
exec = "cursor $SLICE_CODE_DIR"
```

## Variable substitution

All hook definitions have access to these variables:

| Variable | Description |
|---|---|
| `$BRANCH` | Branch name (e.g. `tmp/fix-login`) |
| `$SLICE_CODE_DIR` | Absolute path to the worktree |
| `$SLICE_STATE_DIR` | Absolute path to the state directory |
| `$SLICE_PROJECT_DIR` | Project root directory |
| `$PG_PORT`, etc. | Port variables from your process definitions |
| `$@` | All arguments passed after the hook name |
| `$1`, `$2`, `$3`... | Individual positional arguments |
| `${@:2}` | All arguments from the 2nd onward |

## Precedence

Hooks can be defined in three places, in order of precedence:

1. **Template output** — defined in the TOML your `thinslice.new-state` produces. Project-specific.
2. **Project hook file** — `thinslice.hooks.toml` in your repo root. Checked into version control.
3. **Global hook file** — `~/.config/thinslice/hooks.toml`. Personal defaults across all projects.

A hook defined in the template output overrides a project-level hook with the same name, which overrides a global hook.

This means you can define a global `claude` hook that works for all your projects, and override it in a specific project's template if that project needs a different layout.

## Example global hooks

```toml
# ~/.config/thinslice/hooks.toml

[hooks.new-worktree.claude]
tmux = """
new-window -n $BRANCH
split-h
send-left "cd $SLICE_CODE_DIR && claude $@"
send-right "cd $SLICE_CODE_DIR && slice up"
focus-prev
"""

[hooks.new-worktree.codex]
tmux = """
new-window -n $BRANCH
split-h
send-left "cd $SLICE_CODE_DIR && codex $@"
send-right "cd $SLICE_CODE_DIR && slice up"
focus-prev
"""

[hooks.new-worktree.editor]
exec = "$EDITOR $SLICE_CODE_DIR"

[hooks.up.dev]
tmux = """
new-window -n $BRANCH
split-v 70
send-top "$SHELL"
send-bottom "slice logs --follow"
focus-top
"""
```
