Skip to content

Pre-commit Hooks Guide

This document explains the pre-commit hooks setup (.husky/pre-commit) in the ERP-Unlocked monorepo and how to use them effectively.

The repository uses Husky and lint-staged to automatically format and lint your code, verify Python lockfiles, and enforce changesets before each commit. This ensures consistent code quality and release tracking across all contributors.

When you run git commit, .husky/pre-commit runs the following checks automatically:

1. Lint-Staged (Runs on Staged Files Only)

Section titled “1. Lint-Staged (Runs on Staged Files Only)”

The pre-commit hook runs these tools on your staged files:

TypeScript/JavaScript/Astro/Markdown/JSON/YAML Files (*.{ts,tsx,js,jsx,astro,json,md,yml,yaml})

Section titled “TypeScript/JavaScript/Astro/Markdown/JSON/YAML Files (*.{ts,tsx,js,jsx,astro,json,md,yml,yaml})”
  • Prettier formatting (pnpm exec prettier --write)
  • Ruff linting with auto-fix (uv run --with ruff ruff check --fix)
  • Ruff formatting (uv run --with ruff ruff format)

When a Python pyproject.toml file is staged, .husky/pre-commit verifies that the corresponding uv.lock is in sync using uv lock --locked:

  • Dagster (apps/dagster/pyproject.toml): Verifies apps/dagster/uv.lock via cd apps/dagster && uv lock --locked. Blocks the commit (exit 1) if out of sync.
  • Root uv workspace (pyproject.toml, packages/pdf-shared/pyproject.toml, apps/temporal-worker/pyproject.toml): Verifies root uv.lock via uv lock --locked. Blocks the commit (exit 1) if out of sync.

If you stage changes under apps/ or packages/, .husky/pre-commit verifies that a .changeset/*.md file is also staged in the commit:

  • ✅ Passes: If at least one .changeset/*.md file is staged alongside your apps/ or packages/ changes
  • ❌ Blocks (exit 1): If staged changes under apps/ or packages/ lack a staged .changeset/*.md file, prompting you to run pnpm changeset and git add .changeset/*.md
  • 🔄 Skips: Automatically skipped for merge commits

When you clone the repository and run pnpm install, Husky is automatically initialized via the prepare script:

Terminal window
pnpm install

This installs all dependencies and sets up the Git hooks.

If hooks aren’t working, you can manually trigger setup:

Terminal window
pnpm prepare

Just commit as usual! The hooks run automatically:

Terminal window
git add .
git commit -m "feat: add new feature"

The hooks will:

  1. Format and lint your staged files
  2. Check for changesets (if needed)
  3. Complete the commit if everything passes

⚠️ Use sparingly! You can skip hooks if absolutely necessary:

Terminal window
git commit --no-verify -m "emergency hotfix"

When to bypass:

  • Emergency hotfixes that need immediate deployment
  • Fixing broken CI/CD pipelines
  • Addressing critical production issues

When NOT to bypass:

  • Regular feature development
  • Refactoring
  • Documentation updates

You can also run formatters manually:

Terminal window
# TypeScript/JavaScript/Astro/Markdown
pnpm format
# Python
pnpm format:python
# Check formatting without changing files
pnpm format:check
Terminal window
# Prettier
npx prettier --write path/to/file.ts
# Ruff
uv run ruff format path/to/file.py
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "avoid",
"endOfLine": "lf",
"plugins": ["prettier-plugin-astro"],
"overrides": [
{
"files": "*.astro",
"options": {
"parser": "astro"
}
}
]
}

Excludes generated files, build outputs, dependencies, and lock files from formatting.

{
"lint-staged": {
"*.{ts,tsx,js,jsx,astro,json,md,yml,yaml}": ["pnpm exec prettier --write"],
"*.py": [
"bash -c 'command -v uv >/dev/null 2>&1 && uv run --with ruff ruff check --fix \"$@\" || echo \"⚠️ uv not found, skipping Python linting\"' --",
"bash -c 'command -v uv >/dev/null 2>&1 && uv run --with ruff ruff format \"$@\" || echo \"⚠️ uv not found, skipping Python formatting\"' --"
]
}
}

For developers using the pre-commit framework (optional):

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
hooks:
- id: ruff
args: [--fix]
- id: ruff-format

To use this:

Terminal window
# Install pre-commit (one-time)
pip install pre-commit
# Install hooks
pre-commit install
# Run manually on all files
pre-commit run --all-files
  1. Check if hooks are installed:

    Terminal window
    ls -la .husky/

    You should see a pre-commit file.

  2. Reinstall hooks:

    Terminal window
    pnpm prepare
  3. Verify Git hooks directory:

    Terminal window
    git config core.hooksPath

    Should output: .husky

If you get merge conflicts in formatted code:

  1. Accept the incoming changes
  2. Run the formatter:
    Terminal window
    pnpm format
    # or for Python
    pnpm format:python
  3. Stage and commit the formatted files

Some linting errors require manual fixes. If the pre-commit hook fails:

  1. Review the error messages
  2. Fix the issues manually
  3. Stage the fixes: git add .
  4. Try committing again

If hooks are slow on large commits:

  • Commit in smaller chunks to reduce the number of files checked
  • Use --no-verify only for emergency situations

If .husky/pre-commit blocks your commit (exit 1) because you staged files under apps/ or packages/ without a staged .changeset/*.md file:

Terminal window
# For versioned changes, create and stage a changeset:
pnpm changeset
git add .changeset/*.md
git commit -m "feat: add new feature"
# For non-versioned changes (docs, configs, etc.) that touched apps/ or packages/:
git commit --no-verify -m "docs: update README"
  1. Let the hooks run - They catch issues early
  2. Stage incrementally - Easier to review what’s changing
  3. Read error messages - They usually tell you exactly what to fix
  4. Format before reviewing - Makes diffs cleaner
  1. Don’t worry about style - Hooks ensure consistency
  2. Focus on logic - Style is automated
  3. Check for --no-verify - Flag commits that bypassed hooks
  1. Update pre-commit config when tools change
  2. Keep linter versions in sync with CI/CD
  3. Document exceptions if certain files need different rules

The same checks run in CI/CD pipelines:

Terminal window
# What runs in CI
pnpm lint # Lints all apps and packages
pnpm lint:python # Lints all Python code
pnpm format:check # Verifies formatting
pnpm test # Runs all tests

This ensures that even if someone bypasses local hooks, CI will catch issues.

If you encounter issues with pre-commit hooks:

  1. Check this documentation
  2. Review error messages carefully
  3. Ask in the team chat
  4. Create an issue in the repository

Remember: Pre-commit hooks are here to help, not hinder. They save time in code review and ensure consistent quality across the entire codebase.