Install
$ agentstack add skill-paldom-python-skills-python-precommit ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
python-precommit
Set up and maintain the pre-commit framework in the user's Python repository. The value is not "write a YAML file" — a competent model can do that. The value is avoiding the specific traps that make hook setups silently useless: unpinned or drifted hook versions that disagree with CI, ruff hooks that auto-fix and let the commit through anyway, mypy hooks running in an empty environment, the archived Prettier mirror, and slow hooks that train everyone to type --no-verify.
When NOT to use
- Claude Code / agent hooks (PostToolUse, settings.json, AGENTS.md rules,
which enforcement layer should run what) — the agent-guardrails skill, if installed, owns that.
- CI workflow authoring beyond the single pre-commit mirror job below
(matrices, required checks, caching, action SHA pinning at scale) — the python-ci skill.
- Choosing ruff rules or fixing lint/format errors — the python-lint skill.
- Choosing or configuring the type checker itself — the python-typing skill.
- Release automation that consumes conventional commits (version bumps,
changelogs, publishing) — the python-release skill. This skill only enforces the message format at commit time.
- **Repo-level secret scanning / push protection / Dependabot for package
dependencies** — the python-supply-chain skill.
Operating principles
- The hook is convenience; CI is enforcement.
git commit --no-verify
bypasses everything, silently, with no audit trail. Never present a hook as a guarantee. Every check in .pre-commit-config.yaml must also run in CI (switowski.com/blog/pre-commit-vs-ci).
- Commit stage stays fast — a few seconds. Slow hooks are the main driver
of --no-verify habits. Slow checks go to pre-push or CI.
- Pin everything. Every
repo:gets a pinnedrev:; an unpinned or
branch-pinned hook is both irreproducible and a supply-chain hole (installing hooks clones and executes arbitrary repos — pre-commit#942).
- Never enable a hook that fails on the existing tree. Run it over all
files, fix the fallout, commit, then it starts gating.
Workflow
1. Survey the repo
Before writing config, check:
ls .pre-commit-config.yaml .git/hooks/pre-commit 2>/dev/null # existing setup?
grep -n 'ruff\|mypy' pyproject.toml # versions to keep hooks in lockstep with
ls .github/workflows/ 2>/dev/null # workflows -> add check-jsonschema; CI mirror target
ls package.json 2>/dev/null # Node already present? -> Prettier is cheap here
Record the ruff (and mypy) versions in the dev dependency group — hook revs must match them, or local and CI results will disagree (the "double accounting" problem, step 7).
2. Write the Python baseline
.pre-commit-config.yaml at the repo root. Pin every rev (tags shown; see step 7 for SHA freezing), then run pre-commit autoupdate once to resolve current releases and review the diff — the revs below are known-good examples, not the latest.
# .pre-commit-config.yaml
default_install_hook_types: [pre-commit, commit-msg, pre-push]
repos:
# Hygiene — cheap, universal
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
args: [--allow-multiple-documents]
- id: check-toml
- id: check-json
- id: check-added-large-files
- id: check-merge-conflict
- id: detect-private-key
# Python lint + format. Order matters: fixer first, formatter second.
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.10 # keep in lockstep with the ruff version in your dev deps
hooks:
- id: ruff-check # named `ruff` on older revs
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
Two load-bearing details:
--exit-non-zero-on-fix: without it, ruff silently fixes the file and the
hook passes — but the fix is left unstaged, so the commit ships the unfixed content. With it, the hook fails, the developer stages the fix and recommits. That fail → git add → recommit loop is expected pre-commit behavior; tell the user so once.
- Excludes under pre-commit are special: pre-commit passes filenames
explicitly, which can bypass exclusions from a pyproject.toml in an excluded subdirectory (ruff#9585). Put excludes in the top-level ruff config, and for certainty add a hook-level exclude: ^(vendored|migrations)/ regex in the pre-commit config.
3. Non-Python files — format AND validate
Formatting is not validation: a malformed GitHub workflow or pyproject.toml formats fine. Add both layers. Default (Prettier via the maintained fork — the original pre-commit/mirrors-prettier is archived, broken since Prettier v3; older tutorials still point at it):
- repo: https://github.com/rbubley/mirrors-prettier # maintained fork
rev: v3.8.2
hooks:
- id: prettier
types_or: [yaml, markdown, json] # never .py/.pyi — no fight with ruff
args: [--prose-wrap=always]
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.25
hooks:
- id: validate-pyproject
additional_dependencies: ["validate-pyproject-schema-store[all]"]
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.37.1
hooks:
- id: check-github-workflows
- id: check-dependabot
If the team rejects the Node dependency ("Node tax"), use the Node-free stack instead: mdformat for Markdown, taplo for TOML, yamllint for YAML — more tools, zero npm. Recipes, trade-offs, and the yamllint-vs-Prettier conflict are in [references/config-recipes.md](references/config-recipes.md).
4. Commit-message stage — conventional commits
Only if the user wants commit-format enforcement (it feeds changelog/release automation, which the python-release skill owns):
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v4.0.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
commit-msg hooks only run if that hook type is installed — default_install_hook_types (already in the baseline) handles this. Without it, plain pre-commit install installs only the pre-commit stage and the check silently never runs.
5. Pre-push stage — slow checks
mypy and tests are too slow for every commit. Run them once per push via repo: local hooks that use the project's own environment (this also sidesteps the mirrors-mypy isolated-env pitfall — see gotchas):
- repo: local
hooks:
- id: mypy
name: mypy (pre-push)
entry: uv run mypy .
language: system
pass_filenames: false
stages: [pre-push]
- id: pytest
name: pytest (pre-push)
entry: uv run pytest -q
language: system
pass_filenames: false
stages: [pre-push]
language: system means the tool must exist in each clone — fine here because uv run resolves the project's locked versions (pip fallback: entry: python -m mypy .). Budget: pre-push under ~30 s; anything slower is CI-only.
6. Install and first run
uv add --dev pre-commit # pins pre-commit itself in uv.lock
uv run pre-commit install # installs every stage in default_install_hook_types
uv run pre-commit run --all-files
(Without uv: python -m pip install pre-commit, then the same pre-commit … commands — noted once, applies to all commands in this skill.)
The first run --all-files will be slow (hook environments are built on demand) and will likely fail while fixing files — re-run until clean, review, and commit the formatting changes separately from the config. Hooks are per-clone and not tracked by git, so add the install step to the contributor docs or a make setup target; CI is the only backstop for clones that never ran it.
7. Mirror in CI and keep versions in sync
Local hooks and CI must run the same config at the same versions. The mirror job (this one job is in scope here; everything else CI belongs to the python-ci skill):
# .github/workflows/pre-commit.yml
name: pre-commit
on:
pull_request:
push: { branches: [main] }
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # pin actions to full SHAs in real use
- uses: actions/setup-python@v5 # (re-tagged releases are a real attack)
with: { python-version: "3.12" }
- run: python -m pip install pre-commit==4.3.0 # match your uv.lock version
- run: pre-commit run --all-files --show-diff-on-failure
Because CI runs the identical .pre-commit-config.yaml, hook revs cannot drift between local and CI. What still drifts: the hook rev vs the same tool's version in your dev dependency group (ruff in both places is the classic). Keep them in lockstep; automate rev updates with one of — Dependabot's pre-commit ecosystem, pre-commit.ci's scheduled autoupdate, or a monthly pre-commit autoupdate PR (reviewed, never blind). For security-sensitive repos, pre-commit autoupdate --freeze pins revs to full commit SHAs, immune to re-tagging. Details, the pre-commit.ci autofix debate, and the maintenance-only status of pre-commit/action are in [references/sync-and-ci.md](references/sync-and-ci.md).
8. Verify
python3 "${CLAUDE_SKILL_DIR}/scripts/check_precommit_config.py" .pre-commit-config.yaml # footgun lint, read-only
uv run pre-commit run --all-files # exits 0 on a clean tree
git commit --allow-empty -m "bad msg" # commit-msg hook must reject (if step 4 used)
The script (in this skill's folder) checks for unpinned revs, the archived Prettier mirror, missing --exit-non-zero-on-fix, mirrors-mypy without additional_dependencies, unscoped Prettier, wrong hook order, and stages that were configured but never installed. It never modifies anything.
Output spec — what done looks like
.pre-commit-config.yamlat the repo root; every remote repo has a
pinned rev (tag at minimum, SHA via --freeze where warranted).
default_install_hook_typescovers every stage any hook uses.pre-commit run --all-filesexits 0 on the current tree.- A typical commit completes its hooks in a few seconds.
- CI runs the identical config (
pre-commit run --all-filesjob or
pre-commit.ci) — no separately-versioned ruff/mypy invocations duplicating the hooks.
- A rev-update mechanism exists (Dependabot / pre-commit.ci / scheduled
autoupdate PR).
- Contributor docs or Makefile mention
pre-commit install. scripts/check_precommit_config.pyreports 0 errors.
Failure modes & gotchas
- Ruff "fixed" the files but the commit went through with the old content
→ --exit-non-zero-on-fix missing from the ruff hook args (step 2).
- Commit fails, developer confused, files "already fixed" → auto-fix hooks
leave fixes unstaged by design; git add -u and recommit. Say this once in contributor docs.
- Passes locally, fails in CI (or vice versa) → version skew: hook
rev
vs dev-dependency vs whatever CI installs. Fix by running the same config in CI and syncing revs (step 7). This "double accounting" is the top recurring pre-commit complaint.
- Ruff lints files that are in its exclude list → pre-commit passes paths
explicitly; nested-config excludes may be bypassed (ruff#9585). Top-level excludes + hook-level exclude: regex.
- mypy hook reports import errors / misses real errors →
pre-commit/mirrors-mypy runs in an isolated env without your project's dependencies, and only sees staged files (no whole-program view). Either add additional_dependencies: [types-…, pydantic, …] and accept partial checking, or prefer the local uv run mypy . pre-push hook (step 5).
- Prettier hook broken or frozen →
pre-commit/mirrors-prettieris
archived; use rbubley/mirrors-prettier.
- Prettier and ruff fight over files → always scope Prettier with
types_or: [yaml, markdown, json]. Note the type identifier for TypeScript is ts, not typescript — the wrong name silently matches nothing.
- yamllint fails files Prettier just formatted → their defaults genuinely
conflict (comment spacing, indentation); reconcile configs explicitly (see references/config-recipes.md) or drop one of them.
- Hooks that fight each other, reformatting in a loop → order is fixer →
formatter (ruff-check --fix then ruff-format); formatters last.
- commit-msg / pre-push hooks configured but never firing → that hook type
was never installed. default_install_hook_types or pre-commit install --hook-type commit-msg --hook-type pre-push.
- New contributor has no hooks at all → per-clone install, by design.
Document it; rely on the CI mirror as backstop.
- Everyone bypasses with
--no-verify→ the hooks are too slow or too
noisy; that is a design failure, not a discipline failure. Profile with pre-commit run --verbose, move slow hooks to pre-push/CI, and offer SKIP= git commit … as the surgical alternative. Do not promise any local mechanism can prevent bypass.
- First run downloads the world → expected; environments are cached in
~/.cache/pre-commit afterward. Warn users once.
- Coverage thresholds or full test suites in the pre-commit stage →
community consensus is these belong in CI (or at most pre-push); commit-time coverage gates get bypassed and resented.
Unsettled debates — present options, don't assert winners
- Autofix in CI — dominant stance: CI enforces, never mutates
(pre-commit run --all-files, fail on diff). pre-commit.ci's autofix (auto-commits formatting to PRs) is a deliberate exception some teams adopt; note it has documented gaps auto-fixing .github/workflows files.
- prek (github.com/j178/prek) — Rust,
uv-powered, drop-in reader of the same .pre-commit-config.yaml, faster installs/updates (community-reported) and native monorepo support; newer and less battle-tested. Reasonable to offer when speed or monorepos hurt; uv add --dev prek && uv run prek install.
- "Pre-commit is dead under agents" — a minority position (Massdriver
talk, mid-2026) argues high-frequency agent edits make local hooks impractical and checks should consolidate in CI. Most current stacks keep pre-commit as the fast local layer; if the user's repo is agent-heavy, note the trade-off and let them choose. Which layer (agent hook vs git hook vs CI) runs which check is the agent-guardrails skill's territory.
Bundled material
- [references/config-recipes.md](references/config-recipes.md) — complete
annotated configs: full baseline, Prettier vs Node-free stacks, validators, conventional-commit and pre-push recipes, uv lockfile hooks, optional secret scan, ruff-in-Markdown, monorepo/local-hook patterns.
- [references/sync-and-ci.md](references/sync-and-ci.md) — the double
accounting problem, CI mirror recipes, pre-commit.ci, rev-update automation, SHA freezing and hook supply chain, bypass honesty, prek, stage-placement budgets.
- [scripts/checkprecommitconfig.py](scripts/checkprecommitconfig.py) —
read-only footgun linter for an existing .pre-commit-config.yaml (python3 "${CLAUDE_SKILL_DIR}/scripts/check_precommit_config.py" [path] [--strict]; non-zero exit on findings).
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Paldom
- Source: Paldom/python-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.