Back to Blog

5 Git Hooks to Automate Away Your Most Annoying Code Review Comments

5 Git Hooks to Automate Away Your Most Annoying Code Review Comments

I used to get incredibly defensive during code reviews. I’d spend three days perfecting a complex state machine, only to have a senior developer leave six comments about trailing spaces, a stray console.log, or a missing test file. It felt like they were ignoring my logic to focus on trivia. Eventually, I realized the problem wasn't their pickiness—it was my "noise." I was forcing humans to do a machine's job.

If you find yourself apologizing for "dumb mistakes" in PR threads, you need to move your quality gates closer to your keyboard. Git hooks allow you to script checks that run locally before your code ever hits GitHub.

Here are five hooks that will make you look like a much more disciplined engineer than you probably are.

1. The "Only Staged" Linter

Running a full project lint on every commit is a nightmare. If your project has 500 files, you aren't going to wait 40 seconds every time you want to save a tiny change.

The secret is to use a pre-commit hook that only looks at the files you’ve actually touched. If you're in the JavaScript ecosystem, lint-staged is the industry standard. It prevents "lint-drift" without killing your productivity.

The Setup:
In your package.json:

"lint-staged": {
  "*.{js,ts,tsx}": [
    "eslint --fix",
    "prettier --write"
  ]
}

The Hook (`.git/hooks/pre-commit`):

#!/bin/sh
npx lint-staged

Now, if you try to commit a file with a missing semicolon or a non-standard indentation, Git will literally refuse to create the commit until you fix it (or until the hook fixes it for you).

2. The Secret Leak Detector

We’ve all done it. You’re debugging an API integration, you hardcode the STRIPE_SECRET_KEY just for a second to see if it works, and then—*oops*—it’s in the main branch.

Instead of waiting for a security bot to email you a frantic warning after the push, use a pre-commit hook to grep for common patterns like "BEGIN RSA PRIVATE KEY" or "AWS_SECRET_ACCESS_KEY".

The Hook:

#!/bin/sh
# Prevent committing files that look like they contain secrets
KEYWORDS="API_KEY|SECRET|PASSWORD|AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY"

if git diff --cached | grep -Eiq "$KEYWORDS"; then
  echo "❌ Error: You are attempting to commit a potential secret."
  echo "Check your staged changes or use --no-verify if this is a false positive."
  exit 1
fi

It’s a blunt instrument, but it’s better than a leaked credential.

3. The "No Fixme" Enforcer

I have a habit of writing // FIXME: fix this before merging and then promptly forgetting about it. These comments are like landmines for your future self.

You can use a hook to block any commit that contains "TODO" or "FIXME" in the staged changes. Or, if you find that too aggressive, set it to block "DEBUGGER" or "console.log" so you don't accidentally ship your troubleshooting tools to production.

The Hook:

#!/bin/sh
if git diff --cached | grep -q "debugger"; then
  echo "🚨 Wait! You left a 'debugger' statement in your code."
  exit 1
fi

4. Branch Name Validator

If your team uses Jira, Linear, or Trello, your project manager probably wants the ticket ID in the branch name (e.g., feat/PROJ-123-new-login). When you forget this, it breaks the automation that links PRs to tickets.

Instead of trying to remember the format, use a pre-push hook to validate your branch name against a Regex.

The Hook (`.git/hooks/pre-push`):

#!/bin/sh
local_branch="$(git rev-parse --abbrev-ref HEAD)"

# Regular expression for: feat|fix|docs|refactor/PROJ-[number]-description
valid_branch_regex="^(feat|fix|docs|refactor)\/PROJ-[0-9]+-[a-z0-9-]+$"

if [[ ! $local_branch =~ $valid_branch_regex ]]; then
    echo "❌ Push rejected! Your branch name '$local_branch' is wrong."
    echo "Use: <type>/PROJ-123-description"
    exit 1
fi

5. The "Did the Tests Pass?" Gate

The pre-push hook is the final boss. Unlike pre-commit, which should be fast, pre-push can afford to take a few extra seconds. This is where you run your unit tests.

If you push code that breaks the build, you’re wasting CI/CD minutes and potentially blocking your teammates. Catching a failing test locally saves you the "Walk of Shame" when the GitHub Actions tab turns red.

The Hook:

#!/bin/sh
echo "Running unit tests before pushing..."

npm test -- --watchAll=false --bail
# The --bail flag stops the test suite at the first failure

if [ $? -ne 0 ]; then
 echo "❌ Tests failed. Fix them before pushing."
 exit 1
fi

A Note on Friction

The biggest argument against Git hooks is that they "get in the way." If you’re in a flow state and just want to save your progress, a failing lint check is annoying.

You have two escape hatches:
1. The `--no-verify` flag: If you really need to commit something messy right now, use git commit -m "wip" --no-verify. This bypasses the hooks.
2. Husky: If managing raw shell scripts in .git/hooks feels too "low level," use Husky. It keeps your hooks in your repository so your entire team can share the same automated standards.

By the time my code gets to a human reviewer now, the conversation is about architecture, performance, and logic. The "annoying" stuff was handled by a shell script while I was still typing. It makes the review process faster for them and significantly less embarrassing for me.