Skip to main content

Hooks

Hooks let gji run your setup and cleanup commands automatically at key lifecycle moments.

If you only need to copy gitignored local files such as .env.local into new worktrees, use gji sync-files instead of a custom hook. Hooks are best for commands that actually need to run.

Example

{
"hooks": {
"afterCreate": ["pnpm", "install"],
"afterEnter": ["printf", "switched to %s\n", "{{branch}}"],
"beforeRemove": "pnpm run cleanup"
}
}

This is the simplest way to make each new worktree feel “ready” instead of empty.

Available hooks

HookWhen it runs
afterCreateAfter gji new or gji pr creates a worktree.
afterEnterAfter gji go switches to a worktree.
beforeRemoveBefore gji remove deletes a worktree.

Template values

Hooks receive these template variables:

  • {{branch}}
  • {{path}}
  • {{repo}}

The same values are also available as environment variables:

  • GJI_BRANCH
  • GJI_PATH
  • GJI_REPO

Safer hook commands

Prefer argv-array hooks when you do not need shell features:

{
"hooks": {
"afterCreate": ["pnpm", "install"],
"afterEnter": ["printf", "switched to %s at %s\n", "{{branch}}", "{{path}}"]
}
}

Array hooks run without a shell. Each array item is passed as exactly one argument, so branch names and paths containing spaces or shell metacharacters are not re-parsed as shell syntax.

String hooks are still supported for shell features like &&, pipes, redirects, shell functions, and nvm use. Template values are interpolated before the shell parses the command, so avoid putting {{branch}}, {{path}}, or {{repo}} directly into shell strings.

For shell-string hooks, read the context from environment variables and double-quote each expansion:

{
"hooks": {
"afterCreate": "pnpm install && printf 'ready: %s\n' \"$GJI_PATH\""
}
}

Avoid unquoted template values in shell strings, such as echo {{branch}} or cd {{path}}.

Layering behavior

Hooks can come from:

  1. global config
  2. per-repo global overrides
  3. local .gji.json

Different hook keys merge together. The same hook key is overridden by the highest-precedence layer.

That makes it possible to have a general default like afterEnter, while one repository overrides only afterCreate.

Manual execution

You can rerun any hook in the current worktree:

gji trigger-hook afterCreate
gji trigger-hook afterEnter
gji trigger-hook beforeRemove

This is useful after cloning on a new machine or when you need to rebuild a worktree’s setup without recreating it.

Practical examples

Install dependencies for each new worktree

{
"hooks": {
"afterCreate": ["pnpm", "install"]
}
}

Print context on entry

{
"hooks": {
"afterEnter": ["printf", "switched to %s at %s\n", "{{branch}}", "{{path}}"]
}
}