This error occurs when Git's commit-msg hook rejects your commit message. The hook validates message format (like conventional commits) and exits non-zero when validation fails. Fix by formatting your message correctly or bypassing the hook.
The commit-msg hook is a Git hook that runs after you write your commit message but before the commit is finalized. It receives the path to a temporary file containing your commit message as its only argument. If the hook script exits with a non-zero status code, Git aborts the commit entirely. This hook is commonly used to enforce commit message conventions in a project. Popular tools like commitlint, gitlint, and custom validation scripts use this hook to ensure all commits follow a consistent format such as Conventional Commits (e.g., `feat: add new feature`, `fix: resolve bug`). When the hook fails, it means your commit message didn't pass the validation rules defined in the hook script. The commit is rejected, but your message is preserved in `.git/COMMIT_EDITMSG` so you can fix and retry without retyping everything.
The commit-msg hook typically outputs exactly what's wrong with your message. Look for output like:
husky - commit-msg hook exited with code 1 (error)
subject may not be empty [subject-empty]
type must be one of [feat, fix, docs, style, refactor, test, chore] [type-enum]This tells you exactly which rules your message violated.
If your project uses Conventional Commits, format your message as:
<type>(<optional scope>): <description>
[optional body]
[optional footer]Valid types include:
- feat: - New feature
- fix: - Bug fix
- docs: - Documentation changes
- style: - Formatting, no code change
- refactor: - Code restructuring
- test: - Adding/updating tests
- chore: - Maintenance tasks
Examples of valid messages:
git commit -m "feat: add user authentication"
git commit -m "fix(api): resolve null pointer in login handler"
git commit -m "docs: update README with installation steps"When a commit fails, your message is saved to .git/COMMIT_EDITMSG. You can recover and edit it:
# View your rejected message
cat .git/COMMIT_EDITMSG
# Create a new commit with the saved message, opened in editor
git commit -e -F .git/COMMIT_EDITMSGOr create an alias for easy recovery:
git config alias.recommit '!git commit -F "$(git rev-parse --git-dir)/COMMIT_EDITMSG" --edit'
# Then use:
git recommitIf you suspect the hook itself is broken, inspect it:
# View the hook script
cat .git/hooks/commit-msg
# Or if using Husky
cat .husky/commit-msgCommon issues to look for:
- Wrong shebang line (should be #!/usr/bin/env sh or similar)
- Script not executable (chmod +x .git/hooks/commit-msg)
- Windows line endings causing "cannot execute binary file" error
- Missing dependencies (commitlint not installed)
Fix permissions:
chmod +x .git/hooks/commit-msg
# Or for Husky
chmod +x .husky/commit-msgIf you see "cannot execute binary file" or similar, the hook file likely has Windows (CRLF) line endings:
# Check for CRLF endings
file .husky/commit-msg
# Convert to Unix line endings
sed -i 's/\r$//' .husky/commit-msg
# Or using dos2unix
dos2unix .husky/commit-msgEnsure your editor and Git are configured for LF:
git config --global core.autocrlf inputIf you need to commit urgently and will fix the message later:
git commit --no-verify -m "WIP: temporary commit"Warning: Use this sparingly. The hook exists for a reason, and bypassing it regularly defeats its purpose. Some CI/CD pipelines will still validate commit messages and reject non-compliant ones.
If using commitlint, ensure it's properly configured:
# Check if commitlint.config.js exists
cat commitlint.config.jsA basic configuration should look like:
module.exports = {
extends: ['@commitlint/config-conventional'],
};Test your message manually:
echo "feat: test message" | npx commitlintIf commitlint isn't installed:
npm install --save-dev @commitlint/cli @commitlint/config-conventional### Conventional Commits Specification
Conventional Commits is a lightweight convention for structuring commit messages. The full specification is:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]Breaking changes can be indicated by:
- Adding ! after type/scope: feat!: drop support for Node 12
- Adding BREAKING CHANGE: in the footer
### Husky v9 Setup
For modern Husky (v9+) with commitlint:
npm install --save-dev husky @commitlint/cli @commitlint/config-conventional
npx husky init
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg### Custom commit-msg Hooks
You can write custom validation in any language. Here's a simple shell example:
#!/usr/bin/env sh
# .git/hooks/commit-msg
commit_msg=$(cat "$1")
# Require minimum length
if [ ${#commit_msg} -lt 10 ]; then
echo "Error: Commit message must be at least 10 characters"
exit 1
fi
# Require conventional commit format
if ! echo "$commit_msg" | grep -qE "^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?!?: .+"; then
echo "Error: Commit message must follow Conventional Commits format"
exit 1
fi
exit 0### Pre-commit Framework
If using pre-commit framework with gitlint:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/jorisroovers/gitlint
rev: v0.19.1
hooks:
- id: gitlint### Debugging Hook Failures
To see exactly what's happening in your hook:
# Add debug output to hook
set -x # Add at top of scriptOr run Git with trace output:
GIT_TRACE=1 git commit -m "test message"warning: BOM detected in file, this may cause issues
UTF-8 Byte Order Mark (BOM) detected in file
fatal: Server does not support --shallow-exclude
Server does not support --shallow-exclude
warning: filtering out blobs larger than limit
Git partial clone filtering large blobs warning
fatal: Server does not support --shallow-since
Server does not support --shallow-since in Git
kex_exchange_identification: Connection closed by remote host
Connection closed by remote host when connecting to Git server