setup-pre-commit adds a pre-commit hook to a JavaScript or TypeScript repo. A pre-commit hook is a script Git runs inside git commit, before the commit is made. If the script fails, Git stops and no commit is made.
This hook does three things in order: format the staged files with Prettier, run the type check, run the tests. So when an agent commits broken code, the commit is blocked and the agent gets an error message to fix.
format the staged files with Prettier
run the type check
run the tests
This hook does three things in order
When to use it
A JavaScript or TypeScript repo has no checks at commit time, and you want formatting, type checking and tests to run on every commit.
You ask to add pre-commit hooks, set up Husky or configure lint-staged.
When not to use it
You want to stop an agent running dangerous git commands like git push or git reset --hard. That's git-guardrails-claude-code: it hooks into Claude Code, not into Git.
You want checks that run on every pull request, on a fresh machine. That's CI (see CI/CD). A pre-commit hook runs on the machine making the commit, and git commit --no-verify skips it.
The repo isn't JavaScript or TypeScript. The Skill installs npm packages. For Python, the pre-commit framework does the same job.
What it needs to start
A package.json in the same folder as .git. Husky won't install into a parent folder, so a package.json in a subfolder needs a manual setup (see Going deeper).
A lockfile, so it can pick npm, pnpm, yarn or bun. With none, it uses npm.
Ideally, typecheck and test scripts in package.json. If one is missing, it leaves that line out of the hook and tells you.
What it creates
husky, lint-staged and prettier as dev dependencies.
A .husky/ folder, and a prepare script set to "husky", so the hook is set up again after each install.
.husky/pre-commit, with npx lint-staged, then npm run typecheck, then npm run test. lint-staged only touches staged files, so it's fast; the type check and tests run on everything.
.lintstagedrc, which runs Prettier on all staged files.
.prettierrc, but only if there's no Prettier config yet.
One commit, "Add pre-commit hooks (husky + lint-staged + prettier)". It goes through the new hook, as a smoke test.
Going deeper
6 min read · Going deeper not read
Why it exists
Matt explained the idea in an article updated on 2026-01-16. When agents work on their own, "you need feedback loops so the AI can verify its own work." His third loop is Husky, which "enforces feedback loops before every commit." The payoff: "If any step fails, the commit is blocked and the AI gets an error message."
The top of Matt's feedback-loops article, updated Jan 16, 2026. From aihero.dev, 30 October 2026.
Why it suits agents so well, in his words: "AI agents don't get frustrated by repetition. When code fails type checking or tests, the agent simply tries again."
He added setup-pre-commit to his repo on 2026-02-10, with the same three hook lines the article shows. Today it sits in his misc bucket: "Tools I keep around but rarely use, not promoted in the plugin." On 2026-07-01 he made misc a non-promoted bucket: "misc/ skills are no longer installable via the plugin". Since 2026-10-06 his notes call these Skills "frozen", and "Bug reports, feature requests and PRs against them are closed." His advice if one breaks: "install it with skills.sh and fix your copy: the files are yours to edit."
From snapshot/web/git-scm.com/docs-githooks.md, lines 300–304: “This hook is invoked by git-commit[1], and can be bypassed with the --no-verify option. It takes no parameters, and is invoked before obtaining the proposed commit log message and making a commit. Exiting with a non-zero status from this script causes the git``commit command to abort before creating a commit.”
Git runs the hook, if it can. A Git hook is a program Git runs "at certain points in git's execution". "Hooks that don't have the executable bit set are ignored." That's why the Skill's last check is that .husky/pre-commit "exists and is executable". Git also runs every hook from "the root of the working tree", which is why Try it starts the hook with cd web.
Husky does the setup. Husky's docs say "Git needs to be configured and husky needs to setup files in .husky/."
lint-staged puts the fixes back. After Prettier formats your staged files, lint-staged "automatically restages them". So the commit holds the formatted version.
It's a fix to the environment. This is the mechanical half of Fix the environment. Matt's v1.3 notes say retro turns a mechanical mistake into "a deterministic check (a linter rule, a pre-commit hook, or a CI job)".
The Python world's version. The pre-commit framework does the same job for any language. It calls itself "a multi-language package manager for pre-commit hooks." You install it with pip install pre-commit, and its docs suggest adding it to a Python project's requirements file. You list hooks in a file named .pre-commit-config.yaml, then "run pre-commit install to set up the git hook scripts". That's the one to reach for in the practice project's api/ folder.
A real example
Matt's own app, course-video-manager, shows the pattern growing. On 2026-01-15, the day before the article's 2026-01-16 update, a commit he made with Claude added Husky and Prettier. Its hook was one line, npx lint-staged (lint-staged then ran Prettier, the type check and the tests), and its message promised to "Provide immediate feedback to LLMs and developers".
By 2026-10-23 that hook ran 20 commands, from pnpm exec lint-staged to pnpm run check:complexity. The newest line came after "Five agent worktrees were committed as gitlinks". The fix was to "fail check:static / pre-commit on any future gitlink." That mistake became one more line in the hook.
Commit da04bf4 in course-video-manager: stray agent worktree gitlinks removed, and a guard added. From github.com, 30 October 2026.
Where it goes wrong
Claude Code issue #40117 (Mar 28): the agent bypassed pre-commit hooks with --no-verify, stash and quiet flags. From github.com, 30 October 2026.
Husky's How To page: most Git commands take -n/--no-verify to skip hooks. From typicode.github.io, 30 October 2026.
The agent skips the hook. Git says the pre-commit hook "can be bypassed with the --no-verify option", and Husky's docs show git commit -m "..." -n. Chris Richardson found on 2025-09-10 that "Claude Code would regularly bypass the precommit hook by using git commit --no-verify." His fix: deny the agent direct git commit, and make it "commit changes through a tool provided by an MCP server." In a Claude Code bug report from 2026-03-28, "6 consecutive commits landed with failing integration tests", despite a rule saying "Do not use --no-verify on git commit." Its author added permissions.deny rules for --no-verify, plus a pre-push hook that blocks a push when the pre-commit hook's success marker is missing. A bot closed the report on 2026-05-07 for inactivity. So a hook is a fast check, not a guarantee. Matt's own list of checks names both "a pre-commit hook, or a CI job". git-guardrails-claude-code doesn't block --no-verify by default, but it asks whether you want to add patterns to its list. It's no guarantee either: Matt's notes list reports that its hook is bypassed by git global options and fails open without jq (#301, #898).
package.json in a subfolder. "Husky doesn't install in parent directories (../) for security reasons." One user ran npx husky init in a docs/ folder and got ".git can't be found". Even after following the docs' fix, users in #1437 (2024) saw commits go through without the hook; the maintainer pointed back to the docs. So always check that the hook really fires, as Try it does.
Newer Bun projects. Step 1 only looks for bun.lockb, but "Bun v1.2 changed the default lockfile format to the text-based bun.lock." Users reported it in #14 (2026-03-19) and #1088 (2026-09-16). The reply closing #14 is marked as "generated by AI during triage", so it isn't Matt's own word. With no match the Skill falls back: "Default to npm if unclear." The Skill is frozen, so fix your own copy.
Try it
In your practice project (set it up first with keeping track's Try it step if you haven't), run setup-pre-commit on the TypeScript website in web/. Its package.json sits in a subfolder, so expect the Skill's npx husky init step to fail.
Check web/package.json has typecheck and test scripts. The Skill leaves out any that are missing and tells you.
Start your agent in web/ and ask it to set up pre-commit hooks with setup-pre-commit.
When husky init reports ".git can't be found", have the agent set the prepare script to cd .. && husky web/.husky, then run npm run prepare. The Skill's own check expects prepare to be "husky"; here it won't be, and that's fine.
Have it start web/.husky/pre-commit with cd web, because Git runs hooks from the repo's root. The Skill's three lines follow.
Break a type in web/ on purpose and try to commit. The commit should stop with the error. If it goes through, the hook isn't firing: recheck steps 3 and 4.
Check that your CI workflow, .github/workflows/ci.yml, runs the same type check, for the day someone uses --no-verify.
For api/, try the pre-commit framework the same way.
From snapshot/skills/setup-pre-commit/SKILL.md, lines 41–47: “npx lint-staged npm run typecheck npm run test ``` Adapt: Replace npm with detected package manager. If repo has no typecheck or test script in package.json, omit those lines and tell the user.”
From snapshot/skills/setup-pre-commit/SKILL.md, lines 81–85: “Stage all changed/created files and commit with message: Add pre-commit hooks (husky + lint-staged + prettier) This will run through the new pre-commit hooks: a good smoke test that everything works.”
From snapshot/web/pre-commit.com/index-html.md, lines 29–32: “It is a multi-language package manager for pre-commit hooks. You specify a list of hooks you want and pre-commit manages the installation and execution of any hook written in any language before every commit.”
From snapshot/skills/setup-pre-commit/SKILL.md, line 19: “Check for package-lock.json (npm), pnpm-lock.yaml (pnpm), yarn.lock (yarn), bun.lockb (bun). Use whichever is present. Default to npm if unclear.”