# What are Git hooks?

Git hooks are scripts that Git runs automatically at set points, e.g. before a commit, so a team can check or change the work at each step.

Last updated September 29, 2026, 8 min read

## Learning objectives

After reading this article you will be able to:

-   Define Git hooks
-   Identify how hooks are shared across a team
-   Explain client-side and server-side hooks

## Related content

-   [What is CI/CD?](https://specstory.com/learning/ci-cd/ci-cd)
-   [What is a pre-commit hook?](https://specstory.com/learning/ci-cd/pre-commit-hook)
-   [What is the difference between pre-commit, pre-push, and CI checks?](https://specstory.com/learning/ci-cd/pre-commit-vs-pre-push-vs-ci)
-   [What are agent hooks (Stop hooks)?](https://specstory.com/learning/ci-cd/agent-hooks)

## What are Git hooks?

Git hooks are programs that Git starts at fixed points in its own workflow, e.g. before it records a [commit](https://specstory.com/learning/glossary#commit). A hook that runs before an action can stop the action by exiting with a nonzero code. Client-side hooks run on a developer's machine, and server-side hooks run on the server that receives a push.

The [Git documentation](https://git-scm.com/docs/githooks) describes hooks as "programs you can place in a hooks directory to trigger actions at certain points in git's execution."

More generally, a hook is a script that a tool runs at a set point in its workflow. Git hooks that run before an action usually check the work, e.g. the [pre-commit hook](https://specstory.com/learning/ci-cd/pre-commit-hook), which checks the staged changes before each commit. Hooks that run after an action can only react, e.g. by sending a notice after a push. A continuous integration and delivery ([CI/CD](https://specstory.com/learning/ci-cd/ci-cd)) pipeline runs the checks again later, on a server the whole team shares.

## How do Git hooks work?

Git runs a hook in five steps:

1.  A person or a tool runs a Git command, e.g. `git commit`.
2.  Git looks for an executable file with the hook's name in the hooks folder, which is `.git/hooks` unless the `core.hooksPath` setting names another.
3.  Git starts the file and passes it arguments, environment variables, or lines on standard input, depending on the hook.
4.  The hook runs its check and exits with a code.
5.  For a hook that runs before an action, exit code 0 lets the action continue and any other code stops it. A hook that runs after an action cannot change its outcome.

A repository created with `git init` usually holds sample hooks whose names end in `.sample`, which Git ignores. A hook can be written in any language, e.g. Python, if the file is executable. A script's first line names its interpreter. Most of Git's samples are shell scripts.

Git defines more than 20 hooks. These run when someone commits or pushes:

| Hook | Where and when it runs | Can it stop the action? |
| --- | --- | --- |
| `pre-commit` | Client, before Git asks for the commit message | Yes |
| `prepare-commit-msg` | Client, after Git writes the default message | Yes |
| `commit-msg` | Client, after the message is written | Yes |
| `post-commit` | Client, after Git records the commit | No |
| `pre-push` | Client, before `git push` sends anything | Yes |
| `pre-receive` | Server, once per push, before any branch changes | Yes, for the whole push |
| `update` | Server, before each branch or tag changes | Yes, for that one |
| `post-receive` | Server, after the branches change | No |

## What is an example of a Git hook?

Here is an illustrative example. Acme Co. sells furniture online. Its team wants each commit message to start with a ticket id, e.g. `CHK-12:`. A `commit-msg` hook receives the path of the message file and can reject or edit the message. A developer at Acme saves this hook as `.githooks/commit-msg`, runs `chmod +x` on it, commits it, and runs `git config core.hooksPath .githooks` in the clone:

```bash
#!/bin/sh
# $1 is the file that holds the proposed commit message
if ! head -n 1 "$1" | grep -qE '^CHK-[0-9]+: '; then
  echo "commit-msg: start the message with a ticket id, e.g. CHK-12:" >&2
  exit 1
fi
```

The developer asks a [coding agent](https://specstory.com/learning/ai-coding/coding-agent) to "Let customers edit their delivery address during checkout." The commit then runs in five steps:

1.  The agent edits and stages `checkout/address.js`, then runs `git commit -m "Let customers edit the delivery address"`.
2.  Git writes the message to `.git/COMMIT_EDITMSG` and starts the hook with that path as its argument.
3.  The message has no ticket id, so the hook prints its error line and exits with code 1. Git records no commit.
4.  The agent receives the error line as its command's output. It copies the id from that line and commits again with the message `CHK-12: Let customers edit the delivery address`.
5.  The hook exits with code 0, and Git records the commit.

The hook checked the message, not the code. The address saved, but the cart emptied. This example is simplified. A real repository would also run [linting](https://specstory.com/learning/code-review/linting) in a pre-commit hook and the tests in CI.

## What changes when a coding agent writes the code?

A coding agent gets a hook's error line as the output of its failed Git command, and it can edit its work to match that line. The `commit-msg` hook in the example checks the format of the ticket id, not whether the ticket exists or fits the change. The hook accepted the example id that the agent copied from the error line. In a fresh sandbox clone, Git runs none of the team's hooks unless a setup script installs them.

Where a hook lives also changes what a reviewer can check. A hook in `.git/hooks` sits outside version control, so an edit to it never appears in a diff. A hook in a committed folder, e.g. `.githooks`, is a file the agent can also edit, but the edit shows up in the pull request.

[Agent hooks](https://specstory.com/learning/ci-cd/agent-hooks) are a separate mechanism. The agent's harness runs them inside its loop even when the agent does not commit. A line in [AGENTS.md](https://specstory.com/learning/ai-coding/agents-md) that tells the agent to install the hooks is text the agent reads, not a check that runs. A team can install Git hooks in the agent's setup script and run each check that must pass again on a server.

## What are the limits of Git hooks?

Git does not copy hooks when someone clones a repository. A team shares hooks in one of these ways:

-   **A committed folder.** The team commits its hook scripts, and each developer runs `git config core.hooksPath .githooks` once per clone.
-   **A hook manager.** A tool, e.g. Husky, installs the hooks from committed files during project setup.
-   **Hooks in Git config.** In Git 2.54 and later, a Git config file, e.g. a user's global config, can define hooks with `hook.<name>.command` and `hook.<name>.event` settings.

Each way needs a step per machine. Hooks have other limits:

-   **Anyone can skip them.** The `--no-verify` flag skips the `pre-commit` and `commit-msg` hooks of `git commit` and the [pre-push hook](https://specstory.com/learning/ci-cd/pre-commit-vs-pre-push-vs-ci) of `git push`.
-   **Some commits skip them.** A code host's web editor never runs a local hook.
-   **A hook checks only what its script checks.** Many hook checks use [static analysis](https://specstory.com/learning/code-review/static-analysis) or read the commit message, and never start the software.
-   **Slow hooks get skipped.** People often bypass a hook that slows every commit.

A passing hook shows that its check passed, not that the software works.

## How are client-side hooks different from server-side hooks?

Client-side hooks run on the machine where someone runs a Git command, so that person can change or skip them. Server-side hooks run in the repository that receives a push, and the person pushing cannot skip them. The `pre-receive` hook runs once per push and can reject all of it. The `update` hook runs once per branch or tag and can reject only that one. A server-side hook can run [secret scanning](https://specstory.com/learning/glossary#secret-scanning) and reject a push that contains a key.

[Pro Git](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) advises enforcing a policy on the server, because clones do not copy client-side hooks. Hosted code services, e.g. GitHub.com, usually do not run a customer's own server-side hooks. Teams there use a CI check that must pass before a merge as their [quality gate](https://specstory.com/learning/ci-cd/quality-gate). A webhook, an HTTP request that the host sends to a team's URL after a push, can take over a `post-receive` hook's notices.

Diagram: Where client-side and server-side hooks run

The highlighted hooks can stop the action. The developer controls the hooks above the line, and the server's owner controls the hooks below it.

## FAQs

### Where are Git hooks stored?

Git hooks are stored in the hooks folder inside a repository's .git folder, unless the core.hooksPath setting names another folder, e.g. one committed with the code. In Git 2.54 and later, Git config files can also define hooks by name, with a command and an event for each.

### Are Git hooks shared when you clone a repository?

Git hooks are not shared when someone clones a repository, because Git does not copy the hooks folder. A team shares hooks by committing the scripts and pointing core.hooksPath at them in each clone, or by using a hook manager that each clone installs once.

### What is a commit-msg hook?

A commit-msg hook is a script that Git runs after the commit message is written and before Git records the commit. Git passes it the path of the file that holds the message. A nonzero exit code stops the commit, and the hook can also edit the message in place.

### What languages can Git hooks be written in?

Git hooks can be written in any language the machine can run, e.g. Python, because Git starts the hook file as a program. The file must be executable, and a script's first line names its interpreter. Most of the sample hooks that Git provides are shell scripts.

---

Source: [What are Git hooks? | Client and server hooks | SpecStory](https://specstory.com/learning/ci-cd/git-hooks)
