Wiring commitizen into an existing repo Jump to heading
A guided prompt solves a specific problem: people who know the convention exists but not what it requires. It is not a substitute for validation, because anyone can bypass the prompt with git commit -m, and it is actively harmful if it becomes the only way to commit — the first time someone is mid-rebase with a conflict and the prompt will not run, they will reach for a workaround that disables everything. This recipe adds it as an option alongside a hook that remains the real gate, within commit message hooks and templates.
When to use this approach Jump to heading
- Your project uses a structured convention such as Conventional Commits.
- New contributors get the format wrong in ways review has to correct.
- The repository already has a Node toolchain, so the dependency costs nothing new.
- Your validation hook rejects often enough to be annoying, which is the signal a prompt helps.
- If your convention is loose, a prompt will feel like an interrogation; use a template instead.
Step 1 — Install it without changing how commits are made Jump to heading
npm install --save-dev commitizen cz-conventional-changelog
# Register the adapter in package.json rather than in a global config
npm pkg set config.commitizen.path='cz-conventional-changelog'
npm pkg set scripts.commit='cz' # Verification: the guided path works and the ordinary path is untouched
npm run commit -- --help >/dev/null && echo "prompt available"
git commit --allow-empty -m "chore: ordinary path still works" && git reset --hard HEAD~1 What changed: npm run commit opens the prompt; git commit behaves exactly as before. Nothing is forced, which is what keeps the escape hatches unused.
Step 2 — Keep the validation hook as the authority Jump to heading
The prompt cannot be relied on, so the hook stays. Point both at the same configuration file, or they will disagree the day someone edits one of them.
// commitlint.config.js — one source of truth for the shape of a message
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'subject-max-length': [2, 'always', 72],
'scope-enum': [2, 'always', ['api', 'web', 'infra', 'deps', 'release']],
},
}; # .husky/commit-msg
npx --no -- commitlint --edit "$1" # Verification: the same rule rejects the same message from either path
echo "feat(unknown): thing" | npx commitlint || echo "scope rejected, as configured" The scope list is the setting that pays for itself. Free-text scopes drift into thirty variants within a year, and a changelog grouped by scope becomes useless — the failure mode described in how to enforce conventional commits with commitlint.
Step 3 — Make the prompt fast enough to prefer Jump to heading
A prompt that asks eight questions is slower than typing the message, and people notice. Trim it to the fields your convention actually requires.
// .czrc equivalent, expressed in package.json config
// Skip questions whose answers are almost always empty.
{
"config": {
"commitizen": {
"path": "cz-conventional-changelog",
"skipQuestions": ["body", "footer"],
"maxHeaderWidth": 72,
"defaultType": "feat"
}
}
} # Verification: time both paths honestly before defending either
time npm run commit -- --dry-run 2>/dev/null || echo "measure with a real commit" Step 4 — Handle the cases where the prompt cannot run Jump to heading
Rebases, merges, amends and commit --fixup all produce messages without an interactive session. The hook must recognise them, and the team needs to know the prompt is optional in those moments.
# These all bypass the prompt by design and must still pass validation
git commit --amend --no-edit
git commit --fixup HEAD~2
git rebase --continue # Verification: the hook exempts generated messages
printf 'fixup! feat(api): add refund endpoint\n' > /tmp/m
npx --no -- commitlint --edit /tmp/m && echo "fixup accepted" Commitlint understands fixup! and squash! prefixes; a hand-rolled hook usually does not until someone hits it during review. The autosquash workflow itself is covered in using fixup commits and autosquash during review.
Step 5 — Document the choice, not the tool Jump to heading
Write down which path is recommended for whom, so the prompt reads as help rather than as policy.
# CONTRIBUTING.md, in three lines that do more than a paragraph:
# New here? npm run commit — asks the questions, gets the format right.
# Comfortable? git commit — the hook checks the result either way.
# Mid-rebase? git commit — the prompt is not available, and that is fine. Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Should we force everyone through the prompt? Jump to heading
No, and attempting it is how teams end up with a repository-wide --no-verify habit. Force the rule, offer the tool: the hook guarantees the outcome, and the prompt exists to make the outcome easy for whoever wants it.
Does the prompt work for contributors from forks? Jump to heading
It does if they install the dependencies, which many drive-by contributors will not. That is another reason the pipeline check matters more than the local tooling — see linting commit messages for forked pull requests.
Is a Node dependency acceptable in a non-JavaScript repository? Jump to heading
Sometimes, but weigh it honestly: it brings a toolchain, a lockfile and an upgrade cadence to a repository that may not otherwise need one. For polyglot projects, the pre-commit framework offers equivalent hooks without the runtime.
Related Jump to heading
- Commit Message Hooks & Templates — the parent topic and where validation belongs.
- Generating a Commit Message Template Per Branch — the lighter alternative to a prompt.
- Adding Trailers Automatically With a commit-msg Hook — structured data the prompt does not collect.