Pre-commit Hooks skill for Codex
Hooks that fix staged files in seconds, not minutes. Codex picks the hook manager that fits the repo, wires formatters and linters to run only on staged files with auto-fix, adds secret and large-file checks, and keeps slow work like full type checks in pre-push or CI. It lands the first mass reformat as a separate commit, times a real commit against a five-second budget, and adds a CI job so skipped hooks still get caught.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- pre-commit-hooks Installs to ~/.agents/skills/pre-commit-hooks/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install pre-commit-hooks The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project pre-commit-hooks Installs to .agents/skills/pre-commit-hooks/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/pre-commit-hooks.zipunzip -o pre-commit-hooks.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) pre-commit-hooks PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $pre-commit-hooks, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Sets up fast git hooks that format and lint only staged files - the pre-commit framework (or prek) for Python and polyglot repos, husky with lint-staged or lefthook for JavaScript and TypeScript - plus secret scanning, commit-msg checks and a CI job that runs the same hooks so --no-verify cannot skip them. Use when asked to add pre-commit hooks, set up husky or lint-staged, speed up slow commit hooks, fix hooks that reformat unstaged work, or make local checks match CI.
Use it when
- A repo has no local checks and broken formatting keeps reaching PRs
- Commit hooks are slow enough that people bypass them
- Hooks are rewriting or losing unstaged work
- Local hook results disagree with CI
Use something else when
- You are configuring the linter rules themselves: use eslint-setup
- You want commit message format rules and commitlint: use conventional-commits
- You need full secret scanning of history and CI: use secret-scanning
How to check the result
Make a test commit with a deliberate formatting error, confirm the hook fixes and stages it in under five seconds, then confirm the CI job fails on the same error when committed with --no-verify.
The mistake it prevents
Running the whole test suite or a full-project type check in pre-commit, which makes hooks slow enough that everyone starts committing with --no-verify.
Good hooks finish in a few seconds, touch only staged files, fix what they can
automatically, and never surprise the developer by rewriting unstaged work. They are a
convenience layer: CI runs the exact same checks on every PR, because anyone can commit
with --no-verify.
Before changing anything
- Read
AGENTS.md,package.jsonscripts,pyproject.toml, and any existing.pre-commit-config.yaml,.husky/,lefthook.ymlor.lintstagedrc*. - Check
git config core.hooksPath; if set, another tool already owns the hooks and a second installer will silently fight it. - Note the linters and formatters already configured. Hooks should call the project’s tools with the project’s config, never introduce a parallel config.
Choose the hook manager
| Repo | Default | Why |
|---|---|---|
| Python, or several languages | pre-commit framework (or prek, a faster compatible reimplementation) | Pinned, isolated tool environments, huge hook ecosystem |
JS/TS with package.json at the root | husky + lint-staged | Uses the project’s own node_modules, zero extra runtime |
| JS/TS monorepo or need parallel hooks across languages | lefthook | Single binary, parallel commands, glob filters, stage_fixed |
| Repo already uses one of these | Keep it | Migrating hook managers is churn with no user benefit |
What belongs in which hook
| Hook | Put here | Keep out |
|---|---|---|
pre-commit | Formatters with auto-fix, fast linters on staged files, merge-conflict markers, large-file and private-key checks, secret scan | Full test suite, type-checking the whole project, anything needing network |
commit-msg | Conventional Commits or ticket-ID check | Anything slow |
pre-push | Type check, affected unit tests, if they finish in under about a minute | Integration tests, builds |
| CI | Everything above on all files, plus the real test suite | - |
Budget: aim for under 5 seconds for a typical commit, with 10 seconds as the hard ceiling. Slower hooks get bypassed, and then they protect nothing.
pre-commit framework
# .pre-commit-config.yaml
default_install_hook_types: [pre-commit, commit-msg]
minimum_pre_commit_version: "3.2.0"
repos:
- 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
- id: check-toml
- id: check-merge-conflict
- id: check-added-large-files
args: [--maxkb=500]
- id: detect-private-key
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.17.3
hooks:
- id: ruff-check
args: [--fix]
- id: ruff-format # after ruff-check --fix, since fixes may need reformatting
- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.0
hooks:
- id: gitleaks
- repo: local
hooks:
- id: mypy-changed
name: mypy (staged files)
entry: uv run mypy
language: unsupported # formerly "system"; runs a tool already on PATH
types: [python]
require_serial: true
pre-commit install # writes hooks for default_install_hook_types
pre-commit autoupdate # pin every repo to its latest tag; review the diff
pre-commit run --all-files # first run on the whole repo; expect fixes
pre-commit run --from-ref origin/main --to-ref HEAD # only files changed on this branch
- The first run clones hook repos and builds isolated environments into
~/.cache/pre-commit; that needs network. In a Codex sandbox with network off, ask for approval or run the underlying tools directly (ruff check --fix,ruff format). language: unsupportedhooks use whatever is installed; prefer them for type checkers that must see the project’s dependencies.autoupdate --freezepins commit SHAs instead of tags, useful for supply-chain policy.- Hooks receive only staged files that match
types/files. A hook that must see the whole project (mypy with cross-module types) needspass_filenames: false, and is usually better inpre-pushor CI.
husky + lint-staged
npm install --save-dev husky lint-staged
npx husky init # creates .husky/pre-commit and sets "prepare": "husky" in package.json
# .husky/pre-commit (husky v9: plain commands, no shebang or sourcing lines needed)
npx lint-staged
// lint-staged.config.js
export default {
"*.{js,jsx,ts,tsx}": ["eslint --fix --max-warnings=0", "prettier --write"],
"*.{json,md,yml,yaml,css}": "prettier --write",
// Function form: run once without file arguments (tsc cannot type-check single files)
"*.{ts,tsx}": () => "tsc --noEmit -p tsconfig.json",
};
- lint-staged stashes unstaged changes, runs on staged content, re-adds fixed files and
restores the rest, so partially staged files are safe. Do not add a manual
git addto the commands. - Running
tscon every commit is often too slow for large projects; move it topre-push(.husky/pre-push) if it exceeds the budget. - Skip hook installation in CI and production installs with
HUSKY=0. - Swap
npxforpnpm execoryarnper the lockfile in the repo.
lefthook
# lefthook.yml
pre-commit:
parallel: true
commands:
lint:
glob: "*.{js,ts,tsx}"
run: npx eslint --fix {staged_files}
stage_fixed: true
format:
glob: "*.{js,ts,tsx,json,md}"
run: npx prettier --write {staged_files}
stage_fixed: true
Install with npx lefthook install (or the binary from your package manager).
Same checks in CI
# GitHub Actions step (pre-commit framework)
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pipx run pre-commit run --all-files --show-diff-on-failure
For husky setups, CI runs the underlying scripts (npm run lint, npm run format:check)
instead of lint-staged, because there is nothing staged in CI.
Workflow
- [ ] 1. Inventory existing hooks, core.hooksPath and tool configs
- [ ] 2. Pick the manager (table above) and the hook stages
- [ ] 3. Add the config, install, run on all files once
- [ ] 4. Commit the mass-format result separately from the hook config
- [ ] 5. Add the commit SHA of that format commit to .git-blame-ignore-revs
- [ ] 6. Time a typical commit; move anything slow to pre-push or CI
- [ ] 7. Add the CI job and document install steps in AGENTS.md / CONTRIBUTING.md
Gotchas
- First adoption on an old repo rewrites hundreds of files. Land that as its own commit (with the user’s approval for the churn) so feature diffs stay readable.
- Two formatters on the same files (Prettier and Biome, Black and Ruff format) oscillate. One formatter per file type.
- Hooks that need network (
npm audit, remote schema fetches) fail offline and in sandboxes. Keep them in CI. git commit --no-verifyandSKIP=hook-id git commitexist for emergencies. Never use them to get past a failing hook without telling the user; fix the cause instead.- A hook that modifies files without auto-staging them leaves the commit missing the fix.
Use lint-staged,
stage_fixed, or the pre-commit framework, which all handle this. - Windows and GUI git clients run hooks without your shell profile; avoid relying on aliases or nvm-loaded PATH entries in hook scripts.
Verify
Test on a throwaway branch with a clean working tree (ask first if it is dirty):
git switch -c tmp/hook-check
printf 'x = 1 \n' > hook_sample.py && git add hook_sample.py # trailing space to fix
time git commit -m "test: hook check" # observe fixes and timing
git show --stat HEAD
git switch - && git branch -D tmp/hook-check # discard the test branch
Report: hooks installed, measured time for a typical commit, which checks moved to pre-push or CI, and the CI job that enforces the same rules.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Pre-commit Hooks"
short_description: "Hooks that fix staged files in seconds, not minutes"
default_prompt: "$pre-commit-hooks Add pre-commit hooks to this Python repo for ruff, formatting and secret scanning" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.