pre-commit hook 必须用 husky 而非纯 shell 脚本,因其自动注入 path、跨平台解析 package.json scripts、避免硬编码路径;手动写 .git/hooks/pre-commit 易因换行符、node 环境变量或 node_modules/.bin 二进制路径失效。

pre-commit hook 为什么必须用 Node.js 的 husky 而不是直接写 shell 脚本
纯 shell pre-commit hook 在 Windows 上路径、换行、Node 环境变量经常失效,尤其当项目依赖 eslint 或 prettier 这类需调用本地 node_modules/.bin 下二进制的情况。husky 不仅自动注入 PATH,还能跨平台解析 package.json 中的 scripts,避免硬编码路径。
实操建议:
- 用
npx husky-init初始化,它会自动改写.git/hooks/pre-commit并在package.json中插入"prepare": "husky install" - 不要手动在
.git/hooks/下新建文件——Git 不会读取未被 husky 管理的 hook - 确保
package.json里有类似"lint:staged": "eslint --fix --ext .js,.ts src/"的 script,然后在 husky hook 里调用它
如何让 pre-commit 只检查本次提交改动的文件(避免全量跑 ESLint)
全量检查拖慢提交速度,尤其在大型仓库中可能卡住 10 秒以上。关键不是禁用 lint,而是精准定位变更文件范围。
实操建议:
- 用
lint-staged配合 husky:它默认只传入git add缓存区中的文件路径给 linter - 在
package.json中配置"lint-staged": { "**/*.{js,ts}": ["eslint --fix"] },再让 husky hook 执行npm run lint-staged - 注意
lint-staged的 glob 匹配基于项目根目录,不是.git目录;若文件在子目录被重命名或移动,需确保git add -A已暂存这些变更
pre-commit 失败后 git commit 仍成功?检查这三项配置
常见现象是 ESLint 报错但提交照样完成,本质是 hook 脚本退出码没被 Git 正确识别为失败。
实操建议:
- 确认 husky hook 脚本末尾没有
|| true或set +e类忽略错误的语句 - 检查
lint-staged是否启用了--allow-empty或--no-stash——它们可能导致跳过实际校验 - 运行
git status --porcelain手动验证当前暂存区是否真有文件;如果输出为空,lint-staged默认不执行任何命令(不会报错,但也不检查)
TypeScript 项目里 tsc --noEmit 检查为何总被跳过
tsc --noEmit 是类型检查最轻量的方式,但它默认只检查 tsconfig.json 中 include 或 files 显式声明的文件。如果新添加的 .ts 文件没被包含,就不会触发报错。
实操建议:
- 在
lint-staged配置中显式传入文件列表:"**/*.ts": ["tsc --noEmit --project tsconfig.json"] - 确保
tsconfig.json的include至少覆盖src/**/*,且不含node_modules或dist - 避免在
pre-commit中直接调用tsc全局命令——优先用npx tsc或./node_modules/.bin/tsc,防止 CI 和本地环境不一致
真正容易被忽略的是:husky 的 hook 脚本执行时工作目录是 Git 仓库根,但某些工具(比如旧版 eslint)会尝试从当前文件所在目录向上查找配置,导致规则没生效。遇到诡异的“本地 OK、CI 报错”问题,先加一句 pwd && ls -a 到 hook 里看实际路径。










