Claude-skill-registry git-hooks
Manage pre-commit hooks, performance optimization, and temp commits. Use when asked about "pre-commit slow", "commit taking forever", "skip hooks", "bypass pre-commit", "optimize commits", or multi-agent workflows. Covers fast Prettier-only hook, ESLint performance issues, and CI delegation strategy.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/git-hooks" ~/.claude/skills/majiayu000-claude-skill-registry-git-hooks-d45eea && rm -rf "$T"
skills/data/git-hooks/SKILL.mdGit Hooks Management
Overview
This monorepo uses a fast pre-commit hook that only runs Prettier (~1-2 seconds). ESLint, typecheck, and tests run in CI.
Why? ESLint with TypeScript type-aware rules takes ~2 minutes per file due to project-wide type loading.
Quick Reference
Normal Commit (~1-2 seconds)
git commit -m "feat: add feature"
Bypass All Checks (instant)
git commit --no-verify -m "message" # or TEMP_COMMIT=1 git commit -m "[temp] message"
Auto-bypass Prefixes
Commits starting with these bypass the hook:
- temporary, will squash[temp]
- work in progress[wip]
- skip CI[skip-ci]
- explicit bypass[no-verify]
What Runs Where
| Check | Pre-commit | CI | Editor |
|---|---|---|---|
| Prettier | ✅ ~1s | ✅ | ✅ |
| ESLint | ❌ (too slow) | ✅ | ✅ |
| TypeScript | ❌ (too slow) | ✅ | ✅ |
| Tests | ❌ | ✅ | manual |
Performance History
Original hook: ~3-5 minutes
- lint-staged (ESLint): ~2 minutes
- Turbo typecheck: ~2 minutes
- Vitest tests: ~90 seconds
Optimized hook: ~1-2 seconds
- Prettier only on staged files
Before Optimizing Your Hook
Benchmark each step to identify the bottleneck:
# Benchmark lint-staged time npx lint-staged # Benchmark ESLint directly on a single file time pnpm exec eslint packages/core/src/index.ts --fix # Benchmark Prettier directly time pnpm exec prettier packages/core/src/index.ts --write # Benchmark typecheck time pnpm turbo run typecheck --filter='[HEAD]'
Performance Comparison (This Repo)
| Approach | Time | Notes |
|---|---|---|
| Full lint-staged (ESLint + Prettier) | ~2:05 | ESLint type-aware rules |
| ESLint alone on 1 file | ~2:00 | Type loading dominates |
| Prettier alone on 1 file | ~0.3s | No type loading |
| Prettier-only hook | ~1-2s | Current approach |
Why ESLint is Slow
The
.eslintrc.js has type-aware linting enabled:
parserOptions: { project: './tsconfig.eslint.json', // This loads entire TS project }
This makes ESLint load and parse the entire TypeScript project for every file. In a monorepo with many packages, this can take 2+ minutes even for a single file.
Diagnosing Slow ESLint Rules
# Show timing for each rule TIMING=1 pnpm exec eslint packages/core/src/index.ts # Output shows which rules are slowest: # Rule | Time (ms) # @typescript-eslint/no-unsafe* | 45000ms <- type-aware rules
Options to Fix
- Remove
option - disables type-aware rules, fastestproject - Use
- identify and disable specific slow rulesTIMING=1 - Skip ESLint in pre-commit - run in CI only (current approach)
- Switch to Biome - faster Rust-based linter
Manual Checks Before PR
Run full validation before creating PRs:
# Full CI-equivalent check pnpm turbo run typecheck lint test # Or individually pnpm exec eslint . pnpm turbo run typecheck pnpm test
Hook Configuration
Location:
.husky/pre-commit
Current hook does:
- Get staged
,.ts
,.tsx
,.json
,.md
files.yml - Run Prettier on them
- Re-stage formatted files
Troubleshooting
Hook still slow?
Check if someone re-added lint-staged:
grep -n "lint-staged\|eslint" .husky/pre-commit
Need ESLint locally?
Run it manually on specific files:
pnpm exec eslint packages/core/src/myfile.ts
CI failing but local passes?
The hook doesn't run ESLint/typecheck. Run locally:
pnpm turbo run typecheck lint
Common ESLint Errors in CI
no-case-declarations: Lexical declarations (
const, let, class, function) in switch case blocks must be wrapped in braces:
// ❌ Bad - ESLint error switch (method) { case 'storage.get': const value = await db.get(key); // Error: Unexpected lexical declaration return res.json({ data: value }); } // ✅ Good - wrap case block in braces switch (method) { case 'storage.get': { const value = await db.get(key); return res.json({ data: value }); } }
This pattern is common in route handlers and bridge API implementations.