Agent Skills: Pre-commit Framework

When setting up automated code quality checks on git commit. When project has .pre-commit-config.yaml. When implementing git hooks for formatting, linting, or validation. When creating prepare-commit-msg hooks to modify commit messages. When distributing a tool as a pre-commit hook.

UncategorizedID: Jamie-BitFlight/claude_skills/pre-commit

Skill Files

Browse the full folder contents for pre-commit.

Download Skill

Loading file tree…

pre-commit/SKILL.md

Skill Metadata

Name
pre-commit
Description
When setting up automated code quality checks on git commit. When project has .pre-commit-config.yaml. When implementing git hooks for formatting, linting, or validation. When creating prepare-commit-msg hooks to modify commit messages. When distributing a tool as a pre-commit hook.

Pre-commit Framework

Configure and implement git hooks using pre-commit or prek for automated code quality checks, formatting, linting, and commit message processing across multi-language projects.

Alternative: prek

prek is a Rust-based reimplementation of pre-commit that offers:

  • Faster execution (Rust vs Python)
  • No Python dependency required
  • Drop-in replacement: Uses same .pre-commit-config.yaml file
  • Identical CLI interface: All commands work the same way

Installation:

# Using uv (recommended)
uv tool install prek

# Using pip
pip install prek

# Using cargo
cargo install prek

Detection: To determine which tool is installed in a repository, read .git/hooks/pre-commit (second line):

  • Contains "pre-commit.com" → pre-commit is installed
  • Contains "github.com/j178/prek" → prek is installed

Throughout this skill: Commands shown with pre-commit work identically with prek. Simply replace pre-commit with prek in any command.

When to Use This Skill

Use this skill when:

  • Setting up git hooks for code quality automation
  • Implementing commit message validation or rewriting workflows
  • Configuring pre-commit hooks for formatting tools (black, prettier, etc.)
  • Creating custom hooks for project-specific quality checks
  • Installing hooks for prepare-commit-msg stage (message modification)
  • Troubleshooting hook installation or execution issues
  • Designing hook definitions for distribution in tool repositories
  • Managing hook stages and execution order

Core Concepts

Hook Stages

Pre-commit supports multiple git hook stages matching git hook names directly:

| Stage | Purpose | Common Use Cases | | -------------------- | --------------------------- | --------------------------------- | | pre-commit | Before commit creation | Code formatting, linting, tests | | prepare-commit-msg | Before message editor opens | Commit message rewriting | | commit-msg | After message written | Message validation only | | pre-push | Before push to remote | Integration tests, security scans | | pre-merge-commit | Before merge commit | Merge validation | | post-checkout | After checkout | Environment setup | | post-commit | After commit created | Notifications, logging | | post-merge | After merge completes | Dependency updates | | manual | Explicit invocation only | On-demand tasks |

Critical Distinction: prepare-commit-msg vs commit-msg

| Feature | prepare-commit-msg | commit-msg | | ------------------------- | --------------------------------------------------------------- | --------------------- | | Can modify message | Yes | No (validation only) | | When it runs | Before editor opens | After message written | | Environment variables | PRE_COMMIT_COMMIT_MSG_SOURCE, PRE_COMMIT_COMMIT_OBJECT_NAME | None | | Use for | Rewriting, formatting | Validation, rejection |

For commit message rewriting: Use prepare-commit-msg stage.

For commit message validation: Use commit-msg stage with tools like commitlint.

Activate the commitlint skill for commit message validation patterns:

Skill(command: "commitlint")

Activate the conventional-commits skill for commit message format standards:

Skill(command: "conventional-commits")

Installation

Install pre-commit or prek Tool

pre-commit (Python-based):

# Using uv (recommended)
uv tool install pre-commit

# Using pip
pip install pre-commit

# Verify installation
pre-commit --version

prek (Rust-based alternative):

# Using uv (recommended)
uv tool install prek

# Using pip
pip install prek

# Using cargo
cargo install prek

# Verify installation
prek --version

Install Hooks in Repository

# Using pre-commit:
# Install default hook type (pre-commit stage only)
pre-commit install

# Install specific hook type (required for prepare-commit-msg)
pre-commit install --hook-type prepare-commit-msg

# Install multiple hook types
pre-commit install --hook-type pre-commit --hook-type prepare-commit-msg

# Install and setup environments immediately
pre-commit install --install-hooks

# Overwrite existing hooks
pre-commit install --overwrite

# Using prek (same commands, just replace 'pre-commit' with 'prek'):
prek install
prek install --hook-type prepare-commit-msg
# ... etc

Configure Default Hook Types

To install prepare-commit-msg automatically with pre-commit install or prek install:

# .pre-commit-config.yaml
default_install_hook_types: [pre-commit, prepare-commit-msg]

Configuration Files

.pre-commit-config.yaml (User Repository)

Place in repository root to configure which hooks to use.

Essential Properties

| Property | Type | Default | Purpose | | ---------------------------- | ---- | -------------- | ------------------------------- | | repos | list | Required | Repository mappings | | default_install_hook_types | list | [pre-commit] | Hook types installed by default | | default_stages | list | all stages | Default stages for hooks | | fail_fast | bool | false | Stop on first hook failure |

Repository Mapping

repos:
  - repo: https://github.com/org/tool
    rev: v1.0.0  # Use immutable ref (tag or SHA)
    hooks:
      - id: hook-name
        stages: [prepare-commit-msg]
        args: [--option, value]

Hook Configuration Properties

| Property | Type | Purpose | | ---------------- | ------ | ---------------------------------- | | id | string | Hook ID from repository (required) | | stages | list | Override hook stages | | args | list | Additional arguments | | files | regex | File pattern to match | | exclude | regex | File pattern to exclude | | types | list | File types (AND logic) | | always_run | bool | Run even without matching files | | pass_filenames | bool | Pass staged files to hook | | verbose | bool | Force output on success |

Example Configuration

# .pre-commit-config.yaml
default_install_hook_types: [pre-commit, prepare-commit-msg]

repos:
  # Standard code quality hooks
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml

  # Python formatting
  - repo: https://github.com/psf/black
    rev: 23.12.1
    hooks:
      - id: black
        language_version: python3.11

  # Commit message processing
  - repo: https://github.com/your-org/commit-polish
    rev: v1.0.0
    hooks:
      - id: commit-polish
        stages: [prepare-commit-msg]

.pre-commit-hooks.yaml (Hook Definition)

Place in hook repository to define available hooks for distribution.

Hook Definition Schema

| Property | Type | Required | Purpose | | ---------------------------- | ------ | ------------------- | ---------------------------------- | | id | string | Yes | Unique hook identifier | | name | string | Yes | Display name during execution | | entry | string | Yes | Command to execute | | language | string | Yes | Hook language (python, node, etc.) | | stages | list | No | Git hooks to run for | | pass_filenames | bool | No (default: true) | Pass staged files to hook | | always_run | bool | No (default: false) | Run without matching files | | files | regex | No | Pattern of files to run on | | exclude | regex | No | Pattern to exclude | | types | list | No | File types (AND logic) | | description | string | No | Hook description | | minimum_pre_commit_version | string | No | Minimum pre-commit version |

Example Hook Definition

# .pre-commit-hooks.yaml
- id: commit-polish
  name: Polish Commit Message
  description: Rewrites commit messages to conventional format using LLM
  entry: commit-polish
  language: python
  stages: [prepare-commit-msg]
  pass_filenames: false  # Hook receives message file path
  always_run: true       # Run even without file changes
  minimum_pre_commit_version: '3.2.0'

Implementing prepare-commit-msg Hooks

Hook Arguments

The prepare-commit-msg hook receives:

  1. Positional argument (sys.argv[1]): Path to commit message file (.git/COMMIT_EDITMSG)

  2. Environment variables:

    • PRE_COMMIT_COMMIT_MSG_SOURCE: Message source (message, template, merge, squash, commit)
    • PRE_COMMIT_COMMIT_OBJECT_NAME: Commit SHA (for amend operations)

Implementation Template

#!/usr/bin/env python3
"""Hook entry point for prepare-commit-msg stage."""
import os
import sys


def main() -> int:
    """Entry point for pre-commit hook.

    Returns:
        0 on success, non-zero aborts the commit.
    """
    if len(sys.argv) < 2:
        print("Error: No commit message file provided", file=sys.stderr)
        return 1

    # Get commit message file path from pre-commit
    commit_msg_file = sys.argv[1]

    # Get optional environment info
    source = os.environ.get('PRE_COMMIT_COMMIT_MSG_SOURCE', '')
    commit_sha = os.environ.get('PRE_COMMIT_COMMIT_OBJECT_NAME', '')

    # Read current message
    with open(commit_msg_file, encoding='utf-8') as f:
        original_message = f.read()

    # Skip if message is empty
    if not original_message.strip():
        return 0

    # Process message
    new_message = process_commit_message(original_message)

    # Write back modified message
    with open(commit_msg_file, 'w', encoding='utf-8') as f:
        f.write(new_message)

    return 0  # Success - commit proceeds


def process_commit_message(message: str) -> str:
    """Transform the commit message.

    Args:
        message: Original commit message

    Returns:
        Transformed commit message
    """
    # Implement message transformation logic
    return message


if __name__ == "__main__":
    sys.exit(main())

Entry Point Configuration

Configure entry point in pyproject.toml:

[project.scripts]
commit-polish = "commit_polish.hook:main"

Hook Definition Configuration

# .pre-commit-hooks.yaml
- id: commit-polish
  name: Polish Commit Message
  entry: commit-polish
  language: python
  stages: [prepare-commit-msg]
  pass_filenames: false  # Critical: hook receives message file path
  always_run: true       # Critical: run even without staged files

Running Hooks

Automatic Execution

Hooks run automatically during git operations (works with both pre-commit and prek):

git commit -m "message"  # Runs pre-commit and prepare-commit-msg hooks
git push                 # Runs pre-push hooks

Manual Execution

# Using pre-commit:
# Run all hooks for default stage
pre-commit run

# Run specific hook
pre-commit run commit-polish

# Run specific hook stage
pre-commit run --hook-stage prepare-commit-msg

# Run on specific files (scoped operation - preferred)
pre-commit run --files path/to/file.py path/to/other.py

# Run with verbose output
pre-commit run commit-polish --verbose

# Using prek (identical commands):
prek run
prek run commit-polish
prek run --hook-stage prepare-commit-msg
prek run --files path/to/file.py
prek run commit-polish --verbose

Important - Avoid --all-files Pattern: Running hooks with --all-files formats code throughout the entire repository, not just your current changes. This causes:

  • Diff pollution: Merge requests show formatting changes to files you didn't modify
  • Merge conflicts: Formatting changes on files being worked on by other developers
  • Broken git blame: Mass formatting obscures the actual author of meaningful changes

Preferred Patterns:

  • pre-commit run (no args): Runs on staged files only
  • pre-commit run --files <paths>: Runs on specific files you're working on
  • Git hook auto-execution: Runs automatically on commit for staged files

Exception: Use --all-files ONLY when the user explicitly requests repository-wide cleanup (e.g., "format the entire codebase").

Testing Hooks

# Test hook from local repository
pre-commit try-repo /path/to/hook-repo hook-id --verbose

# Test prepare-commit-msg hooks (provide message file)
pre-commit try-repo /path/to/repo commit-polish \
    --commit-msg-filename .git/COMMIT_EDITMSG

# Test hook manually without pre-commit framework
echo "test message" > /tmp/test-msg
python -m commit_polish.hook /tmp/test-msg
cat /tmp/test-msg

Environment Variables

Skip Hooks

# Skip specific hook
SKIP=commit-polish git commit -m "message"

# Skip multiple hooks
SKIP=commit-polish,trailing-whitespace git commit -m "message"

# Skip all pre-commit hooks
git commit --no-verify -m "message"

Cache Location

# Default cache location
~/.cache/pre-commit

# Override cache location
export PRE_COMMIT_HOME=/custom/path

# Use XDG spec
export XDG_CACHE_HOME=/custom/cache
# Results in: /custom/cache/pre-commit

Common Patterns

Hook Configuration for Commit Message Tools

# Commit message rewriting (prepare-commit-msg)
- repo: https://github.com/your-org/commit-polish
  rev: v1.0.0
  hooks:
    - id: commit-polish
      stages: [prepare-commit-msg]
      pass_filenames: false
      always_run: true

# Commit message validation (commit-msg)
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
  rev: v9.5.0
  hooks:
    - id: commitlint
      stages: [commit-msg]
      additional_dependencies: ['@commitlint/config-conventional']

Language-Specific Formatters

# Python
- repo: https://github.com/psf/black
  rev: 23.12.1
  hooks:
    - id: black
      language_version: python3.11

# JavaScript/TypeScript
- repo: https://github.com/pre-commit/mirrors-prettier
  rev: v3.1.0
  hooks:
    - id: prettier
      types_or: [javascript, jsx, ts, tsx, json, yaml, markdown]

# Rust
- repo: https://github.com/doublify/pre-commit-rust
  rev: v1.0
  hooks:
    - id: fmt
    - id: clippy

Multi-Stage Hooks

# Run formatting on pre-commit, validation on pre-push
- repo: local
  hooks:
    - id: python-tests
      name: Run Python Tests
      entry: uv run pytest
      language: system
      stages: [pre-commit]
      types: [python]
      pass_filenames: false

    - id: integration-tests
      name: Run Integration Tests
      entry: uv run pytest tests/integration
      language: system
      stages: [pre-push]
      pass_filenames: false
      always_run: true

Common Issues

Issue: Hook Not Running

Symptoms: Hook configured but doesn't execute during commits.

Solutions:

  1. Verify hook type is installed:

    ls -la .git/hooks/prepare-commit-msg
    
  2. Install specific hook type:

    pre-commit install --hook-type prepare-commit-msg
    
  3. Check default_install_hook_types in .pre-commit-config.yaml

Issue: pass_filenames: true with Message Hooks

Symptoms: Hook receives staged filenames instead of message file path.

Solution: Set pass_filenames: false for prepare-commit-msg and commit-msg stages:

hooks:
  - id: commit-polish
    stages: [prepare-commit-msg]
    pass_filenames: false  # Critical

Issue: Hook Skipped Without Files

Symptoms: Hook doesn't run when no files match patterns.

Solution: Set always_run: true:

hooks:
  - id: commit-polish
    always_run: true  # Run even without matching files

Issue: Mutable Reference Not Updating

Symptoms: Hook repository updates not reflected after pre-commit autoupdate.

Solution: Use immutable refs (tags or SHAs):

# Wrong: branch names don't auto-update
rev: main

# Correct: tags and SHAs are immutable
rev: v1.0.0
rev: a1b2c3d4

Issue: Hook Execution Order

Symptoms: Hooks run in unexpected order.

Context: Hooks run in the order listed in .pre-commit-config.yaml within each repository. Hooks from different repositories may run in parallel.

Solution: Group dependent hooks in the same repository, or use require_serial: true:

hooks:
  - id: format-code
  - id: lint-code  # Runs after format-code
    require_serial: true

Complete Example: Commit Message Workflow

Repository Structure

commit-polish/
├── .pre-commit-hooks.yaml
├── pyproject.toml
└── src/
    └── commit_polish/
        ├── __init__.py
        └── hook.py

Hook Definition

# .pre-commit-hooks.yaml
- id: commit-polish
  name: Polish Commit Message
  description: Rewrites commit messages to conventional commits format
  entry: commit-polish
  language: python
  stages: [prepare-commit-msg]
  pass_filenames: false
  always_run: true
  minimum_pre_commit_version: '3.2.0'

User Configuration

# User's .pre-commit-config.yaml
default_install_hook_types: [pre-commit, prepare-commit-msg]

repos:
  - repo: https://github.com/your-org/commit-polish
    rev: v1.0.0
    hooks:
      - id: commit-polish
        stages: [prepare-commit-msg]

Installation and Usage

# In user's repository
cd /path/to/user-repo

# Install hooks (both pre-commit and prepare-commit-msg)
pre-commit install

# Make a commit - hook rewrites message automatically
git add .
git commit -m "fix bug"
# Hook transforms message before editor opens

Version Requirements

| Component | Minimum Version | Notes | | ---------- | --------------- | ------------------------------------- | | pre-commit | 3.2.0 | Stage values match hook names | | Python | 3.8+ | For pre-commit framework | | Git | 2.24+ | Required for pre-merge-commit stage |

Related Skills

Activate related skills for comprehensive commit workflow:

  • conventional-commits: Commit message format standards

    Skill(command: "conventional-commits")
    
  • commitlint: Commit message validation rules

    Skill(command: "commitlint")
    

References

See ./references/pre-commit-official-docs.md for complete official documentation links and detailed specifications.

Key Documentation