> ## Documentation Index
> Fetch the complete documentation index at: https://docs.diversion.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Pre-Commit Hooks

> Run custom scripts before commits to validate changes, enforce standards, and automate checks.

Pre-commit hooks let you run custom scripts that automatically validate your changes before each commit. If the script exits with a non-zero code, the commit is blocked - giving you a chance to fix issues before they reach the repository.

## Overview

Hooks are executable scripts stored in a `.dvhooks/` directory at the root of your repository. Because they live inside the repository, they sync to all collaborators just like any other file.

**Common use cases:**

* Lint and syntax checks
* Code formatting enforcement
* Preventing commits of large or forbidden files
* Custom project-specific validation

**How it works:**

* Before a commit, Diversion runs the `.dvhooks/pre-commit` script with the committed files as command-line arguments
* Exit code `0` means the hook passed - the commit proceeds
* Any non-zero exit code means the hook failed - the commit is blocked
* The hook's stdout/stderr output is displayed to the user

<Note>
  Pre-commit hooks are client-side only. They run when committing via the CLI or Desktop app. Commits made directly through the WebApp or API bypass hooks.
</Note>

## Creating a Pre-Commit Hook

<Steps>
  <Step title="Create the hooks directory">
    Create a `.dvhooks` directory at the root of your repository:

    ```bash theme={null}
    mkdir .dvhooks
    ```
  </Step>

  <Step title="Create the hook script">
    Create a `pre-commit` script inside `.dvhooks/`. The script can be written in any language, as long as it's executable.

    <CodeGroup>
      ```bash Mac/Linux (.dvhooks/pre-commit) theme={null}
      #!/bin/sh
      echo "Running pre-commit checks..."

      # Committed files are passed as arguments ($@)
      for file in "$@"; do
        if echo "$file" | grep -q '\.js$'; then
          if grep -q "console.log" "$file"; then
            echo "Error: Remove console.log from $file before committing."
            exit 1
          fi
        fi
      done

      echo "All checks passed."
      exit 0
      ```

      ```bat Windows (.dvhooks/pre-commit.bat) theme={null}
      @echo off
      echo Running pre-commit checks...

      REM Committed files are passed as arguments (%*)
      for %%f in (%*) do (
        echo Checking %%f
      )

      echo All checks passed.
      exit /b 0
      ```

      ```powershell Windows (.dvhooks/pre-commit.ps1) theme={null}
      Write-Host "Running pre-commit checks..."

      # Committed files are passed as arguments ($args)
      foreach ($file in $args) {
        if ($file -match '\.js$') {
          if (Select-String -Path $file -Pattern "console.log" -Quiet) {
            Write-Host "Error: Remove console.log from $file before committing."
            exit 1
          }
        }
      }

      Write-Host "All checks passed."
      exit 0
      ```
    </CodeGroup>

    On Mac/Linux, make the script executable:

    ```bash theme={null}
    chmod +x .dvhooks/pre-commit
    ```
  </Step>

  <Step title="Commit the hook">
    Commit the `.dvhooks/` directory to share the hook with your collaborators:

    ```bash theme={null}
    dv commit .dvhooks -m "Add pre-commit hook"
    ```

    Once committed, every collaborator on the branch will have the hook and it will run automatically before their commits.
  </Step>
</Steps>

## File Arguments

The files being committed are passed to your hook script as **command-line arguments**. Your script can access them via `$@` (Mac/Linux) or `%*` (Windows).

When the file list is too large for a single command invocation, Diversion automatically splits it into chunks and runs the hook multiple times in parallel. If any invocation fails, the commit is blocked.

**Example - check committed files for large binaries:**

<CodeGroup>
  ```bash Mac/Linux theme={null}
  #!/bin/sh
  # Reject files larger than 100MB
  MAX_SIZE=$((100 * 1024 * 1024))
  for file in "$@"; do
    if [ -f "$file" ]; then
      size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)
      if [ "$size" -gt "$MAX_SIZE" ]; then
        echo "Error: $file is too large ($(($size / 1024 / 1024))MB, limit 100MB)."
        exit 1
      fi
    fi
  done

  exit 0
  ```

  ```bat Windows theme={null}
  @echo off
  REM Check committed files
  for %%f in (%*) do (
    echo Checking: %%f
  )
  exit /b 0
  ```
</CodeGroup>

## Environment Variables

Diversion also passes context to your hook script via environment variables:

| Variable            | Description                         |
| ------------------- | ----------------------------------- |
| `DV_REPO_ROOT`      | Absolute path to the workspace root |
| `DV_COMMIT_MESSAGE` | The commit message                  |
| `DV_BRANCH`         | Current branch name                 |

**Example - require a ticket number in the commit message:**

<CodeGroup>
  ```bash Mac/Linux theme={null}
  #!/bin/sh
  # Require commit messages to contain a ticket reference (e.g., PROJ-123)
  if ! echo "$DV_COMMIT_MESSAGE" | grep -qE '[A-Z]+-[0-9]+'; then
    echo "Error: Commit message must include a ticket number (e.g., PROJ-123)."
    echo "Your message: $DV_COMMIT_MESSAGE"
    exit 1
  fi

  exit 0
  ```

  ```bat Windows theme={null}
  @echo off
  REM Require commit messages to contain a ticket reference (e.g., PROJ-123)
  echo %DV_COMMIT_MESSAGE% | findstr /R "[A-Z]*-[0-9]*" >nul 2>&1
  if %errorlevel% neq 0 (
    echo Error: Commit message must include a ticket number (e.g., PROJ-123).
    echo Your message: %DV_COMMIT_MESSAGE%
    exit /b 1
  )

  exit /b 0
  ```
</CodeGroup>

## Skipping Hooks

Use the `--no-verify` flag to bypass pre-commit hooks for a single commit:

```bash theme={null}
dv commit -a -m "quick fix" --no-verify
```

This is useful for urgent fixes when you need to commit without running validation.

## Behavior Details

* **Timeout**: Hooks have a 60-second timeout. If the hook doesn't finish in time, the commit is blocked with a timeout message.
* **Working directory**: The hook runs with the workspace root as its working directory.
* **Output limit**: Hook output is capped at 1 MB. Output beyond that limit is truncated.
* **Output display**: Hook output is always shown to the user, whether the hook passes or fails.
* **Platform detection**:
  * **Mac/Linux**: Diversion looks for an executable file named `pre-commit` in `.dvhooks/`. If the file exists but isn't executable, you'll see an error prompting you to run `chmod +x`.
  * **Windows**: Diversion looks for `pre-commit.bat`, `pre-commit.cmd`, or `pre-commit.ps1` in `.dvhooks/` (checked in that order).
