Lesson 101 of 12120 min read

Git Hooks: Client-Side Hooks — pre-commit, prepare-commit-msg, commit-msg, post-commit

Learn how Git hooks let you run custom scripts automatically at specific points in the commit workflow, covering the four key client-side hooks.

Author: CodersNexus

Git Hooks: Client-Side Hooks — pre-commit, prepare-commit-msg, commit-msg, post-commit

This module's first lesson briefly mentioned the `hooks/` folder inside `.git`. This lesson opens that folder properly: Git hooks are custom scripts that run automatically at specific points in Git's workflow, letting you enforce standards, run automated checks, or trigger side effects — starting with the four most important client-side hooks involved in the commit process.

Learning Objectives

  • Explain what a Git hook is and how it's enabled.
  • Understand the order and purpose of pre-commit, prepare-commit-msg, commit-msg, and post-commit.
  • Write a simple hook script and make it executable.
  • Recognize how a hook can block a commit by exiting with a non-zero status.

Key Terms to Know Before Using Client-Side Git Hooks

  • Git hook: A custom script Git automatically runs at a specific point in its workflow, if a correspondingly-named, executable script exists in the hooks/ directory.
  • pre-commit hook: Runs before a commit message is even prompted for, commonly used to run linters or tests, and can block the commit entirely.
  • prepare-commit-msg hook: Runs after the default commit message is generated but before the editor opens, used to programmatically pre-fill or template the message.
  • commit-msg hook: Runs after the user has written a commit message, commonly used to validate its format, and can reject the commit if the message doesn't comply.
  • post-commit hook: Runs after a commit has been successfully created, used for notifications or other side effects that don't affect whether the commit succeeds.

How Client-Side Git Hooks Actually Work

A **Git hook** is simply an executable script, placed in `.git/hooks/` with a specific, recognized filename (no file extension), that Git automatically runs at the corresponding point in its workflow — if no such script exists (or it exists but isn't executable), Git simply skips that step silently. Git ships with a set of sample hook files (ending in `.sample`) in every repository's `hooks/` folder as a starting reference; to actually activate one, you create a file with the exact expected name (removing `.sample`) and make it executable:

```
chmod +x .git/hooks/pre-commit
```

Four key client-side hooks run, in this order, during the commit process:

**`pre-commit`** runs first, **before** you're even prompted for a commit message. This is the most commonly used hook for **automated quality checks** — running a linter, a test suite, or a code formatter against the staged changes. Critically, if this script **exits with a non-zero status**, Git aborts the commit entirely before it even happens, making pre-commit the standard mechanism for technically enforcing 'don't let broken or non-compliant code get committed' (this module's later lessons on practical hooks and Husky build directly on this exact mechanism).

**`prepare-commit-msg`** runs next, after Git has generated its default commit message content (which might include template text, or — for a merge commit — an auto-generated message) but **before** the editor actually opens for you to write or review it. This hook is used to **programmatically modify or pre-fill** the message — for example, automatically inserting a ticket/issue number parsed from the branch name into the message template.

**`commit-msg`** runs after you've actually written and saved your commit message (whether via `-m` or in the opened editor), and — like `pre-commit` — can **reject the commit** by exiting non-zero. This is the standard mechanism for **enforcing a commit message format or convention** — for example, requiring every message to follow the Conventional Commits pattern from Module 1 (`feat:`, `fix:`, etc.), or requiring a reference to a ticket number.

**`post-commit`** runs last, **after** the commit has already been successfully created — at this point, it's too late for this hook to block or affect the commit itself; it's purely for **side effects**, like sending a notification, triggering a local build, or logging activity, since the commit's success or failure has already been fully determined by this point.

Understanding this order and the specific 'blocking' capability of `pre-commit` and `commit-msg` (versus `prepare-commit-msg` and `post-commit`, which cannot block) is the foundation for the practical, real-world hook examples covered in the next two lessons.

Client-Side Git Hooks Timeline: Visual Walkthrough

Draw a horizontal timeline showing the commit process, left to right, with four hook checkpoints marked along it: 1) 'pre-commit — runs BEFORE message is written; CAN BLOCK the commit (non-zero exit)', 2) 'prepare-commit-msg — runs after default message generated, before editor opens; CANNOT block', 3) 'commit-msg — runs after message is written; CAN BLOCK the commit (non-zero exit)', 4) 'post-commit — runs AFTER commit succeeds; CANNOT block, side-effects only'. Use red highlighting on the two blocking hooks (pre-commit, commit-msg) and a different color for the two non-blocking ones.

Client-Side Git Hooks: Quick Reference Table

HookRuns WhenCan Block the Commit?
pre-commitBefore the commit message is prompted forYes — non-zero exit aborts the commit
prepare-commit-msgAfter the default message is generated, before the editor opensNo — used to modify/pre-fill the message only
commit-msgAfter the user has written the commit messageYes — non-zero exit aborts the commit
post-commitAfter the commit has already succeededNo — purely for side effects/notifications

Writing a Git Hook: Script Example

# Create and enable a simple pre-commit hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
echo "Running pre-commit checks..."
# (in a real hook, this would run a linter or test suite here)
# exit 1 here would BLOCK the commit; exit 0 allows it to proceed
exit 0
EOF
chmod +x .git/hooks/pre-commit

# Now try committing — the hook runs automatically
git add .
git commit -m "test commit"
# Running pre-commit checks...
# [main abc1234] test commit

# Example commit-msg hook enforcing a message format
cat > .git/hooks/commit-msg << 'EOF'
#!/bin/sh
if ! grep -qE '^(feat|fix|docs|chore|refactor|test):' "$1"; then
  echo "ERROR: Commit message must start with feat:, fix:, docs:, chore:, refactor:, or test:"
  exit 1
fi
EOF
chmod +x .git/hooks/commit-msg

Breaking Down the Git Hook Example

The `pre-commit` example demonstrates the minimal required structure of a hook: a shebang line (`#!/bin/sh`), some logic, and an exit code that determines whether the commit is allowed (`exit 0`) or blocked (`exit 1`, not used in this trivial example but noted in the comment). `chmod +x` is essential — Git silently skips a hook file that exists but isn't marked executable. The second example shows a genuinely practical `commit-msg` hook, using `grep` to check whether the commit message (passed to the hook script as its first argument, `$1`, referencing the temporary file containing the message) matches a Conventional Commits-style pattern, exiting with an error and non-zero status — blocking the commit — if it doesn't comply.

How Client-Side Git Hooks Are Used on Real Engineering Teams

  • pre-commit hooks running linters and formatters (like ESLint or Prettier) before allowing a commit are an extremely common practice on professional engineering teams, catching style and quality issues at the earliest possible point.
  • commit-msg hooks enforcing Conventional Commits or a similar structured message format are widely used specifically to support automated changelog generation and semantic versioning tooling that depends on parseable commit messages.
  • post-commit hooks are less commonly used for blocking-style enforcement (since they can't block anything) but are sometimes used to trigger local desktop notifications, update a local dashboard, or kick off a local build automatically after every commit.
  • Because hooks live inside .git/hooks/ (which is NOT tracked by Git itself, a detail the next lesson's discussion of sharing hooks will address), teams needing hooks to be consistently applied across everyone's machine typically rely on a tool like Husky (covered two lessons ahead) rather than manually distributing raw hook scripts.

Git Hooks Interview Questions and Answers

Q1. What is a Git hook, and how do you enable one?

It's a custom script that Git automatically runs at a specific point in its workflow, if a correspondingly-named, executable script exists in the .git/hooks/ directory. You enable one by creating a file with the exact expected name (like pre-commit, with no file extension) and making it executable with chmod +x.

Q2. What is the difference between pre-commit and post-commit in terms of their ability to affect the commit?

pre-commit runs before the commit message is even prompted for and can block the commit entirely by exiting with a non-zero status, making it the standard mechanism for enforcing checks like linting or tests. post-commit runs only after the commit has already successfully been created, so it cannot block anything — it's purely for side effects like notifications, since the commit's outcome is already determined by that point.

Q3. How would you use Git hooks to enforce a specific commit message format?

Use a commit-msg hook, which runs after the user has written their commit message and receives the message content as an argument. The script can check whether the message matches a required pattern (such as Conventional Commits) and exit with a non-zero status to reject the commit if it doesn't comply.

Client-Side Git Hooks Quiz: Test Your Understanding

1. Which client-side hook runs first in the commit process, before the message is even prompted for?

  1. post-commit
  2. pre-commit
  3. commit-msg
  4. prepare-commit-msg

Answer: B. pre-commit

Explanation: pre-commit is the first hook to run in the commit process, executing before the user is even prompted to write a commit message, commonly used for running linters or tests.

2. Which two client-side hooks can actually block a commit from being created?

  1. prepare-commit-msg and post-commit
  2. pre-commit and commit-msg
  3. pre-commit and post-commit
  4. commit-msg and post-commit

Answer: B. pre-commit and commit-msg

Explanation: Both pre-commit and commit-msg run before the commit is finalized and can abort it entirely by exiting with a non-zero status; prepare-commit-msg and post-commit cannot block the commit.

3. What must you do to actually activate a Git hook script?

  1. Nothing — hooks run automatically once written
  2. Create the file with the exact expected name in .git/hooks/ and make it executable
  3. Commit the hook script to the repository first
  4. Configure it in git config

Answer: B. Create the file with the exact expected name in .git/hooks/ and make it executable

Explanation: A hook only runs if a correspondingly-named, executable script exists in the .git/hooks/ directory — an existing but non-executable file (like a leftover .sample) is silently skipped.

Common Mistakes When Using Client-Side Git Hooks

  • Forgetting to make a hook script executable (chmod +x), causing Git to silently skip it without any error or warning.
  • Confusing prepare-commit-msg (modifies the default message, cannot block) with commit-msg (validates the final message, can block).
  • Expecting a post-commit hook to be able to prevent a bad commit, when by the time it runs, the commit has already been successfully created.
  • Assuming hooks are automatically shared with collaborators when a repository is cloned, when .git/hooks/ is not itself tracked by Git (addressed further in the Husky lesson).

Client-Side Git Hooks: Exam-Ready Quick Notes

  • Git hook: executable script in .git/hooks/, run automatically at a specific workflow point; must be named exactly and made executable (chmod +x).
  • Commit process order: pre-commit → prepare-commit-msg → (editor/message written) → commit-msg → (commit created) → post-commit.
  • pre-commit and commit-msg CAN block the commit (non-zero exit). prepare-commit-msg and post-commit CANNOT.
  • hooks/ is not tracked by Git by default — not automatically shared via clone (addressed by Husky, a later lesson).

Client-Side Git Hooks: Key Takeaways

  • Git hooks let you run custom automation at specific points in the commit workflow, without needing any external tooling.
  • pre-commit and commit-msg are the two hooks capable of actually blocking a commit, making them the standard enforcement mechanisms for quality checks and message format.
  • Understanding this exact order and blocking capability is essential groundwork for the practical hook examples and Husky coverage in the next two lessons.

Frequently Asked Questions About Client-Side Git Hooks

Q1. What is a Git hook?

It's a custom script that Git automatically runs at a specific point in its workflow — like before or after a commit — if a correspondingly-named, executable script exists in the .git/hooks/ directory.

Q2. How do I enable a Git hook?

Create a file with the exact expected name (like pre-commit, with no file extension) inside .git/hooks/, write your script, and make it executable with chmod +x — Git silently skips any hook file that isn't executable.

Q3. What is the difference between pre-commit and commit-msg hooks?

pre-commit runs before you're even prompted to write a commit message, commonly used for running linters or tests on the staged changes. commit-msg runs after you've written the message, commonly used to validate that the message follows a required format. Both can block the commit by exiting with a non-zero status.

Q4. Can a post-commit hook prevent a bad commit from being created?

No. By the time post-commit runs, the commit has already been successfully created — this hook is purely for side effects, like sending a notification, since it's too late to affect whether the commit succeeded.

Q5. Are Git hooks automatically shared with everyone who clones my repository?

No. The .git/hooks/ directory is not tracked by Git by default, so hooks aren't automatically included when someone clones your repository. Teams needing hooks consistently applied across everyone's machine typically use a tool like Husky, covered in an upcoming lesson.

Summary

Git hooks are custom, executable scripts placed in `.git/hooks/` with specific, recognized filenames that Git automatically runs at corresponding points in its workflow. Four key client-side hooks run in order during a commit: `pre-commit` runs first, before the message is even prompted for, and can block the commit entirely (non-zero exit) — the standard mechanism for running linters or tests. `prepare-commit-msg` runs next, letting you programmatically modify the default message before the editor opens, but cannot block anything. `commit-msg` runs after the message is written, and can also block the commit — commonly used to enforce a specific message format or convention. `post-commit` runs last, only after the commit has already succeeded, purely for side effects like notifications, since it's too late by that point to affect the commit's outcome. Activating any hook requires creating a file with the exact expected name and making it executable with `chmod +x`, since Git silently skips a hook that exists but isn't executable.

Frequently Asked Questions

It's a custom script that Git automatically runs at a specific point in its workflow — like before or after a commit — if a correspondingly-named, executable script exists in the .git/hooks/ directory.

Create a file with the exact expected name (like pre-commit, with no file extension) inside .git/hooks/, write your script, and make it executable with chmod +x — Git silently skips any hook file that isn't executable.

pre-commit runs before you're even prompted to write a commit message, commonly used for running linters or tests on the staged changes. commit-msg runs after you've written the message, commonly used to validate that the message follows a required format. Both can block the commit by exiting with a non-zero status.

No. By the time post-commit runs, the commit has already been successfully created — this hook is purely for side effects, like sending a notification, since it's too late to affect whether the commit succeeded.

No. The .git/hooks/ directory is not tracked by Git by default, so hooks aren't automatically included when someone clones your repository. Teams needing hooks consistently applied across everyone's machine typically use a tool like Husky, covered in an upcoming lesson.