Docs / Hooks
Lifecycle hooks
Hooks run shell commands at specific points in the worktree lifecycle. They're the mechanism for provisioning anything that isn't a port, database, or env file: registering aliases, seeding data, setting debug flags, cleaning up on exit.
Why hooks exist
GTL allocates resources (ports, databases, env files) declaratively. But some setup steps can't be expressed as config fields. You might need to run a migration, install dependencies, register a router alias, or seed test data. Hooks give you shell access at the right moment in the lifecycle without requiring you to remember a sequence of manual commands.
There are two categories of hooks, designed for different lifecycle moments.
Setup & release hooks
These run during worktree creation and teardown. They're for one-time provisioning: installing dependencies, running migrations, copying credentials, cleaning up artifacts.
| Hook | When it runs | On failure |
|---|---|---|
| pre_setup | Before commands.setup, after env file is written | Setup aborts |
| post_setup | After commands.setup and editor config | Warns, continues |
| pre_release | After release confirmation, before freeing resources | Release aborts |
| post_release | After resources are freed | Warns, continues |
These are defined as array-valued entries under the hooks: block in .treeline.yml. They sit alongside named start hooks in the same block; the parser distinguishes them by value type (arrays vs maps).
hooks:
pre_setup:
- cp .env.template .env.local
post_release:
- rm -rf tmp/cache
Triggers: gtl new, gtl setup, gtl release, gtl prune. These hooks do not run during gtl start or gtl restart.
Start hooks
Start hooks (v0.36.0) run before the server boots and after it exits. They're for per-session provisioning that needs to happen every time you start work on a branch, not just when the worktree is first created.
The canonical example: registering a router alias before the server starts so an OAuth callback URL points at the right branch, then removing it on exit. The alias is session-scoped, not permanent.
Configuration
Start hooks are named entries under the hooks: key. Each entry can have pre_start, post_stop, and auto.
hooks:
oauth:
pre_start: "gtl serve alias myapp-oauth {port}"
post_stop: "gtl serve alias --remove myapp-oauth"
seed:
pre_start:
- bin/rails db:seed
- bin/rails cache:clear
auto: true
Each pre_start and post_stop field accepts a single string or an array of strings. A hook must have at least one command in either field to be recognized. Setting auto: true alone with no commands does nothing.
Activation
Hooks are activated in two ways:
auto: trueruns the hook on every freshgtl startwithout any flag.gtl start --with oauthactivates a specific hook for this session. Accepts comma-separated names:--with oauth,seed.
When both are present, auto hooks run first, then --with hooks in the order specified on the command line. If a hook is both auto: true and named in --with, it runs once (deduplicated).
Note: when multiple hooks have auto: true, their relative execution order is not guaranteed. If ordering matters between auto hooks, use a single hook with multiple commands in its pre_start array.
The lifecycle, step by step
Here's what happens when you run gtl start --with oauth on a fresh start (no supervisor running):
- GTL resolves the active hooks: all
auto: truehooks, plus any named in--with, deduplicated. - Each hook's
pre_startcommands run in sequence. Commands are executed withsh -cin the worktree directory. Stdout and stderr are inherited (you see the output). - If any
pre_startcommand fails, the start aborts. No server process launches. The error message identifies which hook and command failed. - On success, GTL writes a state file recording which hooks were activated. This file lives alongside the supervisor socket (e.g.
/tmp/gtl-xxxxxxxx.hooks). - The env file is regenerated, editor settings are synced, and the supervisor launches
commands.start. - When the supervisor exits (Ctrl+C), GTL reads the hooks state file and runs each hook's
post_stopcommands in reverse activation order. post_stopfailures are logged to stderr but do not block shutdown. The state file is cleaned up regardless.
$ gtl start --with oauth
[hook:oauth] Running pre_start: gtl serve alias myapp-oauth 3010
Alias myapp-oauth.prt.dev → :3010
[hook:seed] Running pre_start: bin/rails db:seed
[hook:seed] Running pre_start: bin/rails cache:clear
[supervisor] Starting bin/dev...
▸ Rails booting on :3010
... working ...
^C
[supervisor] Shutting down...
[hook:seed] No post_stop defined, skipping.
[hook:oauth] Running post_stop: gtl serve alias --remove myapp-oauth
Removed alias "myapp-oauth".
Interpolation
Hook commands support {port} interpolation. The allocated port for the current worktree is substituted at runtime. Multi-port projects also have {port_2} through {port_10}.
Hooks do not support the full env token set. Tokens like {router_domain}, {resolve:...}, and {database} are not interpolated in hook commands. If your hook needs those values, read them from the env file that GTL writes, or reference the environment variables directly since they're already set by the time your server runs.
When hooks don't run
Start hooks are scoped to fresh starts. Several common scenarios skip them:
| Scenario | Hooks run? | Why |
|---|---|---|
Fresh gtl start | Yes | No supervisor running. Full lifecycle. |
Resume (gtl start with supervisor alive) | No | Supervisor is already running. GTL warns if --with was passed. |
gtl restart | No | Restart syncs env and restarts the server process but doesn't re-provision. |
gtl stop | No post_stop | post_stop runs when the supervisor process exits (Ctrl+C), not when the server is stopped remotely. |
gtl start --await | pre_start only | pre_start runs normally, but the calling process exits after the server is healthy. post_stop does not run from this process. |
Hooks are not a process manager
Hooks run before and after the server. They're for quick provisioning steps, not for managing long-running processes. Don't put bundle exec sidekiq & in a pre_start hook. If you need sidecars, put them in your commands.start (e.g. via Foreman, Overmind, or a Procfile) where the supervisor manages their lifecycle.
Good uses for hooks: registering a router alias, seeding a database, toggling a debug flag, warming a cache. Bad uses: starting Sidekiq, running a webpack watcher, launching Redis. If the process needs to stay alive while the server runs, it belongs in commands.start.
Edge cases
- Renaming a hook in YAML. If you rename a hook after a server was started with the old name,
post_stopfor the old name will be silently skipped on exit because the config no longer contains that hook name. Clean up manually if the hook registered external state (like an alias). - Multiple auto hooks. Go's map iteration order is non-deterministic. If you have two auto hooks and their order matters, combine them into one hook with multiple commands.
- Failed pre_start. The hooks state file is only written after all
pre_startcommands succeed. If one fails, no state is persisted andpost_stopwill not run for any hook (since the server never started). - gtl stop vs Ctrl+C.
gtl stopsends a signal to the supervisor from a separate process. Thepost_stophooks run in the originalgtl startprocess after the supervisor exits. If you rangtl startin a terminal that's still open, post_stop will fire. If that terminal was closed, the hooks won't run.
Configuration reference
| Field | Type | Description |
|---|---|---|
| hooks.<name>.pre_start | string or list | Commands to run before the supervisor launches. |
| hooks.<name>.post_stop | string or list | Commands to run after the supervisor exits. Reverse order. |
| hooks.<name>.auto | bool | Run this hook on every fresh gtl start. Default: false. |
| hooks.pre_setup | list | Before commands.setup. Failure aborts setup. |
| hooks.post_setup | list | After commands.setup. Failure warns only. |
| hooks.pre_release | list | After confirmation, before resource teardown. Failure aborts release. |
| hooks.post_release | list | After resource teardown. Failure warns only. |
See it in action
- OAuth callbacks across worktrees: using
pre_startandpost_stopto register and remove a stable router alias for OAuth providers. - Configuration reference: all
.treeline.ymlfields.