Marketplace

pre-commit

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.

$ Instalar

git clone https://github.com/Jamie-BitFlight/claude_skills /tmp/claude_skills && cp -r /tmp/claude_skills/pre-commit ~/.claude/skills/claude_skills

// tip: Run this command in your terminal to install the skill


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:

StagePurposeCommon Use Cases
pre-commitBefore commit creationCode formatting, linting, tests
prepare-commit-msgBefore message editor opensCommit message rewriting
commit-msgAfter message writtenMessage validation only
pre-pushBefore push to remoteIntegration tests, security scans
pre-merge-commitBefore merge commitMerge validation
post-checkoutAfter checkoutEnvironment setup
post-commitAfter commit createdNotifications, logging
post-mergeAfter merge completesDependency updates
manualExplicit invocation onlyOn-demand tasks

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

Featureprepare-commit-msgcommit-msg
Can modify messageYesNo (validation only)
When it runsBefore editor opensAfter message written
Environment variablesPRE_COMMIT_COMMIT_MSG_SOURCE, PRE_COMMIT_COMMIT_OBJECT_NAMENone
Use forRewriting, formattingValidation, 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

PropertyTypeDefaultPurpose
reposlistRequiredRepository mappings
default_install_hook_typeslist[pre-commit]Hook types installed by default
default_stageslistall stagesDefault stages for hooks
fail_fastboolfalseStop 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

PropertyTypePurpose
idstringHook ID from repository (required)
stageslistOverride hook stages
argslistAdditional arguments
filesregexFile pattern to match
excluderegexFile pattern to exclude
typeslistFile types (AND logic)
always_runboolRun even without matching files
pass_filenamesboolPass staged files to hook
verboseboolForce 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

PropertyTypeRequiredPurpose
idstringYesUnique hook identifier
namestringYesDisplay name during execution
entrystringYesCommand to execute
languagestringYesHook language (python, node, etc.)
stageslistNoGit hooks to run for
pass_filenamesboolNo (default: true)Pass staged files to hook
always_runboolNo (default: false)Run without matching files
filesregexNoPattern of files to run on
excluderegexNoPattern to exclude
typeslistNoFile types (AND logic)
descriptionstringNoHook description
minimum_pre_commit_versionstringNoMinimum 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

ComponentMinimum VersionNotes
pre-commit3.2.0Stage values match hook names
Python3.8+For pre-commit framework
Git2.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