| name | pre-commit |
| description | Guide for using pre-commit hooks to validate code formatting, linting, and security checks before commits. You MUST load this skill when using pre-commit hooks for validation. |
| license | MIT |
Pre-commit
Expert guide for pre-commit framework usage, configuration, and custom hook development. Focus on autonomous validation,
diagnostics, and fix workflows.
When to Use
- Analyzing why pre-commit validation blocked a user's local git commit.
- Running mass syntax/lint checks on the repository proactively.
- User needs to configure, update, or bypass a pre-commit hook (markdownlint, yamllint, etc.).
Core Principles
- Staged files only: Pre-commit runs exclusively on staged (cached) files, not the entire working directory.
- Fast feedback: Catch issues before commit/push to reduce CI failures and review cycles.
- Configuration-driven: Hooks reference external config files (.yamllint, .markdownlint.yaml, etc.) for consistency.
- Idempotent fixes: Many hooks auto-fix issues (e.g., trailing-whitespace, end-of-file-fixer); re-stage and re-run to
pass.
- Skip only when necessary: Use
--no-verify or SKIP=<hook-id> sparingly; prefer fixing issues over bypassing
validation.
When Not to Use
- When writing complex, multi-file integration tests that require a full environment boot (pre-commit is for fast, isolated, file-level checks).
- If the repository has explicitly opted out of pre-commit in favor of a different localized linting orchestration tool (like
npm run lint).
- For enforcing subjective architectural patterns that cannot be statically analyzed.
Common Pitfalls
- Bypassing the Cache: Running
pre-commit run on unstaged files and wondering why the linter didn't catch the errors (it only checks the git index by default).
- Auto-Fix Loops: Forgetting to
git add the files that pre-commit auto-formatted (like trailing whitespace fixes), causing the hook to fail repeatedly.
- Mismatched Python Versions: Running a local Python hook that requires
>=3.11 on a system with an older default Python, causing the hook script to crash.
Testing Commands
Run all hooks on all files (recommended before committing):
pre-commit run --all-files
Run all hooks on staged files only (default behavior):
pre-commit run
Run specific hook on all files:
pre-commit run <hook-id> --all-files
Run specific hook on staged files:
pre-commit run <hook-id>
Examples for individual linters:
pre-commit run yamllint --all-files
pre-commit run markdownlint --all-files
pre-commit run flake8 --all-files
pre-commit run black --all-files
pre-commit run ansible-lint --all-files
pre-commit run actionlint --all-files
pre-commit run gitleaks --all-files
pre-commit run detect-secrets --all-files
pre-commit run trailing-whitespace -a
pre-commit run end-of-file-fixer -a
Run hook on specific file:
pre-commit run <hook-id> --files <file-path>
Configuration Dependencies
Pre-commit hooks reference external dotfiles for linting rules. Key dependencies in this repository:
| Hook ID | Config File | Purpose | Notes |
|---|
yamllint | .yamllint | YAML linting rules | Max line length: 120, enforces consistent formatting |
markdownlint | .markdownlint.yaml | Markdown style rules | Max line length: 120, allows consistent heading styles |
yamlfix | .yamlfix.toml | YAML auto-formatting | Line length: 110, forces block-style sequences |
flake8 | CLI args in .pre-commit-config.yaml | Python linting | Max line length: 120 (via --max-line-length=120) |
black | Defaults | Python code formatting | Uses Black's default line length (88) |
ansible-lint | Embedded defaults | Ansible best practices | No custom config file in this repo |
actionlint | Embedded defaults | GitHub Actions validation | No custom config file in this repo |
gitleaks | Optional .gitleaks.toml | Secret scanning patterns | Can override default rules (see below) |
detect-secrets | Optional .secrets.baseline | Secret detection baseline | Can store known false positives |
| All hooks | .editorconfig | Cross-editor consistency | Indent size: 2 for YAML/JSON, 4 for others; LF line endings |
Why trailing-whitespace excludes YAML: The trailing-whitespace hook in the repository's .pre-commit-config.yaml
excludes \.ya?ml$ (YAML files) because yamllint handles trailing spaces for YAML files with warning-level enforcement,
avoiding duplicate checks.
Optional Configuration Files
Some hooks support optional config files to customize behavior beyond defaults:
gitleaks
Default: Uses embedded ruleset for secret detection (API keys, passwords, tokens).
Custom config: Create .gitleaks.toml in repository root to:
- Add custom secret patterns
- Exclude specific paths or files
- Allowlist known false positives
Example .gitleaks.toml:
[extend]
useDefault = true
[[rules]]
id = "custom-api-key"
description = "Custom API Key Pattern"
regex = '''MYAPI-[A-Z0-9]{32}'''
[allowlist]
paths = [
'''.*test.*''',
'''.*example.*'''
]
detect-secrets
Default: Scans all files for high-entropy strings and common secret patterns.
Baseline file: Create .secrets.baseline to store known false positives:
detect-secrets scan > .secrets.baseline
Future runs compare against baseline; only new secrets trigger failures.
j2lint
Default: Validates Jinja2 template syntax using embedded rules.
Custom config: Create .j2lint.toml or .j2lint.yaml for custom linting rules (rarely needed).
Update Process
Update all hooks to latest versions:
pre-commit autoupdate
This updates .pre-commit-config.yaml with the newest rev for each repo. Always review diff and test after updating:
pre-commit run --all-files
Update specific hook:
pre-commit autoupdate --repo https://github.com/<owner>/<repo>
Freeze at specific version: Manually edit .pre-commit-config.yaml and pin rev to a specific tag/commit.
Creating Custom Local Hooks
Pre-commit supports custom hooks via local repository scripts. Add to .pre-commit-config.yaml:
Example: Local Shell Script Hook
- Create script in repository:
#!/usr/bin/env bash
set -euo pipefail
for file in "$@"; do
if ! grep -q "Copyright $(date +%Y)" "$file"; then
echo "Missing copyright header in $file" >&2
exit 1
fi
done
- Make executable:
chmod +x .github/scripts/check-copyright.sh
- Add to
.pre-commit-config.yaml:
- repo: local
hooks:
- id: check-copyright
name: Verify copyright headers
entry: .github/scripts/check-copyright.sh
language: system
types: [python]
Example: Local Python Script Hook
- Create script with uv shebang:
import sys
import yaml
def validate_yaml_keys(filepath):
with open(filepath) as f:
data = yaml.safe_load(f)
if 'required_key' not in data:
print(f"Missing required_key in {filepath}", file=sys.stderr)
return False
return True
if __name__ == '__main__':
all_valid = all(validate_yaml_keys(f) for f in sys.argv[1:])
sys.exit(0 if all_valid else 1)
- Make executable:
chmod +x .github/scripts/validate-yaml-keys.py
- Add to
.pre-commit-config.yaml:
- repo: local
hooks:
- id: validate-yaml-keys
name: Check YAML required keys
entry: .github/scripts/validate-yaml-keys.py
language: system
types: [yaml]
Local Hook Best Practices
- Use
language: system for scripts that require executables in PATH (bash, python, etc.).
- Pass filenames as args: Pre-commit passes matching files as arguments to
entry command.
- Exit codes matter: Exit 0 for success, non-zero for failure.
- Keep scripts idempotent: Hook may run multiple times; avoid side effects.
- Document dependencies: If script requires specific tools, document in script comments or repo README.
Diagnostics and Troubleshooting
Hook fails on specific file:
pre-commit run <hook-id> --files <file-path> --verbose
Skip hooks temporarily (commit without validation):
git commit --no-verify -m "message"
Skip specific hook (run all except one):
SKIP=<hook-id> git commit -m "message"
Check pre-commit installation and config:
pre-commit --version
pre-commit validate-config
Clean pre-commit cache (fixes stale hook errors):
pre-commit clean
pre-commit install --install-hooks
Debug hook execution:
pre-commit run <hook-id> --verbose --all-files
Common Failure Patterns
Auto-fixable hooks modified files: Re-stage and re-run:
git add -u
pre-commit run
YAML formatting conflicts: Run yamllint before yamlfix to catch syntax errors first:
pre-commit run yamllint --all-files
pre-commit run yamlfix --all-files
Markdown line length violations: Reflow paragraphs manually or use editor formatter (VS Code: "Format Document").
Secret detection false positives:
- For gitleaks: Add to
.gitleaks.toml allowlist.
- For detect-secrets: Update
.secrets.baseline with detect-secrets scan --baseline .secrets.baseline.
What to Avoid
- Committing with
--no-verify without documenting reason.
- Modifying
.pre-commit-config.yaml without testing changes locally.
- Bypassing hooks to "save time" — fixing issues post-merge costs more.
- Running hooks on untracked files (pre-commit ignores them; stage first).
Limitations
- Pre-commit only validates staged files; unstaged changes are ignored.
- Hooks run sequentially; large repos may experience slower commit times.
- Some hooks (e.g., yamlfix) may conflict with manual formatting preferences.
- Custom local hooks require executable scripts in repository; not portable across forks without dependencies.
Related Skills
- tdd:
You MUST load this skill when executing test tasks, designing tests, doing TDD, or verifying system behavior.