> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# hooks

The `rs hooks` command installs, updates, and removes repository-level [Git hooks](https://git-scm.com/docs/githooks). Hook scripts run in the project that installed them.

## Usage

```bash
rs hooks [options]
rs hooks uninstall
```

Run `rs hooks` without a subcommand to install or update hooks.

## Install hooks

By default, hook scripts are stored in `.rstack/hooks` at the Git repository root. If the current directory is outside a Git repository, the command skips installation.

Add `rs hooks` to the `prepare` script of the project that owns the repository hooks:

```json title="package.json"
{
  "scripts": {
    "prepare": "rs hooks"
  }
}
```

Run the script once to install the hooks:


```sh [npm]
npm run prepare
```

```sh [yarn]
yarn run prepare
```

```sh [pnpm]
pnpm run prepare
```

```sh [bun]
bun run prepare
```

For example, create a `pre-commit` hook that runs [`rs staged`](/guide/cli/staged.md):

```sh title=".rstack/hooks/pre-commit"
rs staged
```

You can safely run the installation more than once, and it does not load `rstack.config.*`. Run `rs hooks` again after cloning the repository or if the generated hook files are missing.

:::warning Existing Git hook managers

`rs hooks` updates the repository's [`core.hooksPath`](https://git-scm.com/docs/git-config#Documentation/git-config.txt-corehooksPath). If it detects another hooks path or existing Git hooks, it skips installation. Run `rs hooks --force` to let Rstack manage hooks instead.

:::

## Install options

### `--force`

`--force` (or `-f`) installs Rstack hooks even when another Git hook setup already exists:

```bash
rs hooks --force
```

Rstack keeps the existing hook files and points `core.hooksPath` to its generated hooks directory. While this setting is active, Git does not run hooks from the previous location.

`--force` cannot replace hooks owned by another Rstack project.

:::tip
Run `rs hooks --force` only once. Use `rs hooks` without `--force` in the `prepare` script.
:::

### `--hooks-dir`

Specifies a custom directory for hook scripts, relative to the Git repository root.

```bash
rs hooks --hooks-dir config/git-hooks

# Quote paths that contain spaces
rs hooks --hooks-dir "config/git hooks"
```

When using a custom directory, add the full command to the `prepare` script of the project that manages hooks:

```json title="package.json"
{
  "scripts": {
    "prepare": "rs hooks --hooks-dir config/git-hooks"
  }
}
```

> The path must not contain `..`. This prevents hook files from being created or overwritten outside the repository through a parent directory path.

### `--help`

`--help` (or `-h`) displays the command's usage, subcommands, and installation options.

```bash
rs hooks --help
```

## Uninstall hooks

Run the command from the project that installed the hooks:

```bash
rs hooks uninstall
```

Rstack automatically finds the active hooks and removes the `core.hooksPath` setting and the generated `_` directory. It does not delete project hook files such as `.rstack/hooks/pre-commit`. Hooks managed by another project or tool are left untouched.

Remove `rs hooks` from the `prepare` script if you do not want the hooks to be installed again.

If `--force` previously took over hooks in `.git/hooks`, those hooks become active again after uninstalling.

## Hook files

The default directory structure is:

```text
.rstack/
└── hooks/
    ├── pre-commit        # Repository hook script: edit and commit
    └── _/                # Generated by rs hooks; ignored by Git
        ├── .gitignore
        ├── .owner
        ├── runner
        ├── pre-commit
        ├── commit-msg
        └── ...
```

Files alongside `_` are repository hook scripts. The `_` directory contains generated files and is ignored by Git. `rs hooks` points `core.hooksPath` to `.rstack/hooks/_`.

## Supported hooks

Rstack CLI supports these client-side Git hooks:

- `pre-commit`
- `pre-merge-commit`
- `prepare-commit-msg`
- `commit-msg`
- `post-commit`
- `applypatch-msg`
- `pre-applypatch`
- `post-applypatch`
- `pre-rebase`
- `post-rewrite`
- `post-checkout`
- `post-merge`
- `pre-push`
- `pre-auto-gc`

Create a file with the matching name alongside the `_` directory.

## Hook runtime

Rstack CLI runs hook scripts with POSIX `sh -e`. It forwards Git's arguments and standard input, then returns the hook's exit code. Before running a hook, Rstack changes to the project that installed the hooks and prepends that project's `node_modules/.bin` to `PATH`.

### Disable and debug

Set `RSTACK_HOOKS=0` to skip installation or hook execution:

```bash
RSTACK_HOOKS=0 git commit -m "Skip hooks"
```

Set `RSTACK_HOOKS=2` to trace the Rstack CLI hook runtime, including how it invokes the hook script and handles its exit code; to trace commands inside the hook script, add `set -x` to the script:

```bash
RSTACK_HOOKS=2 git commit -m "Trace hooks"
```

### Configure the hook environment

Before running a hook script, Rstack CLI loads this optional POSIX shell file:

```text
${XDG_CONFIG_HOME:-$HOME/.config}/rstack/hooks-init.sh
```

Use it to initialize a Node.js version manager, update `PATH`, or set `RSTACK_HOOKS=0` for the current user.

## Ownership safety

Each generated hooks directory records its owning project. Only that project should include `rs hooks` in its `prepare` script. Rstack skips installation from any other project, even with `--force`, and refuses to remove hooks owned by another project.

To transfer ownership:

1. Remove `rs hooks` from the previous owner's `prepare` script.
2. Run `rs hooks uninstall` from the previous owner.
3. Add `rs hooks` to the new owner's `prepare` script and run it once.

## Monorepo

In a monorepo, the project that provides Rstack CLI may live in a subdirectory such as `frontend/`. Running `rs hooks` there still installs hooks at the Git repository root:

```text
repo/.rstack/hooks/
repo/.rstack/hooks/_/
core.hooksPath=.rstack/hooks/_
```

Rstack records `frontend` as the owning project. The hook scripts stay at the repository root but run from `frontend`, allowing them to use its configuration and dependencies without an explicit `cd`:

```sh title=".rstack/hooks/pre-commit"
rs staged
```

## Worktree behavior

Rstack preserves the Git configuration scope of the active `core.hooksPath`. If a linked worktree uses a worktree-scoped hooks path, `rs hooks --force` replaces it in that scope instead of writing a local setting that Git would ignore.

`rs hooks uninstall` also removes the setting from that scope. Removing a worktree-scoped installation affects only the current worktree and leaves hooks in other linked checkouts unchanged. A local `core.hooksPath` setting is shared across linked checkouts, so changing or removing it affects every checkout that does not override it.

## Troubleshooting

### Hook does not run

- Check that the hook script has a [supported name](#supported-hooks) and is next to the `_` directory.
- Run `git config --show-scope --get core.hooksPath` and verify the effective scope and path.
- Rerun `rs hooks` to restore generated files and executable permissions.
- Check that `RSTACK_HOOKS` is not set to `0` in the environment or initialization file.
- If another hook setup is detected, run `rs hooks --force`.
- If another project is reported as the hooks owner, follow the steps in [Ownership safety](#ownership-safety).

Hook scripts do not need to be executable because Rstack CLI runs them with `sh`.

### Command not found

For exit code 127, Rstack CLI prints the effective `PATH`. If a GUI Git client cannot find Node.js or the package manager, initialize them in `hooks-init.sh`.

### Windows and Yarn

On Windows, hooks run in the POSIX shell included with [Git for Windows](https://gitforwindows.org/). Use LF line endings and `/` path separators in hooks.

[Yarn PnP](https://yarnpkg.com/features/pnp) does not provide `node_modules/.bin`. Run tools through a Yarn script, such as `yarn run test`, and make Node.js and Yarn available through `hooks-init.sh` when needed.
