This feature provides a set of hooks for working with Git repositories.
"features": {
"ghcr.io/tomgrv/devcontainer-features/githooks:11": {}
}npx tomgrv/devcontainer-features -- add githooks
# or, without node/npm:
curl -fsSL https://raw.githubusercontent.com/tomgrv/devcontainer-features/develop/setup.sh | sh -s -- add githooksnpm install --save-dev @tomgrv/devcontainer-features-githooks- pre-commit - Runs git-pre-commit to validate the code before committing and lint-staged to lint and format staged files.
- prepare-commit-msg - Runs commitizen to prepare commit messages.
- commit-msg - Runs commitlint to validate commit messages.
- post-merge - Handles changes in package.json and composer.json after a merge.
- post-checkout - Runs git update to update the current branch with the latest changes from the remote.
- pre-push - Runs validate-branch-name to validate the branch name before pushing.
Hooks are wired up via Husky rather than a raw core.hooksPath .git/hooks symlink setup. Installing husky and installing the hook commands it calls are one step, .husky/install.sh (deployed from stubs/), so hooks work wherever the repo is used:
-
.husky/install.sh- idempotent, never fails its caller:- bootstraps
zz-usefromtomgrv/scriptswhen missing (ZZ_SCRIPTS_SETUP_URLoverrides thesetup.shURL); - installs the
git-hook-*commands ontoPATHviazz-use, and@tomgrv/devcontainer-features-common-utilsglobally whenjson-normalize(used by the lint-staged config) is missing; - runs
husky(local, elsenpx --yes husky) unlesscore.hooksPathis already.husky/_; - when
CLAUDE_ENV_FILEis set, persists the bin dirs onPATHfor the rest of the Claude Code session; HUSKY=0skips it entirely.
- bootstraps
-
It is called from every entry point:
- npm - the
package.json"prepare": "sh .husky/install.sh || ..."script, so a plainnpm installon any machine installs working hooks. (An existingpreparescript in a consumerpackage.jsonis kept as-is by the JSON merge; replace it by hand to opt in.) - devcontainer -
configure-husky.sh(run byfeature-configure githooksonpostCreate). - Claude Code - a
SessionStarthook merged into.claude/settings.json(stubs/.claude/_githooks.settings.json), since claude.ai/code web/cloud sessions clone the repo directly and run neithernpm installnorpostCreate.
- npm - the
-
configure-hooks.shgenerates one thin executable wrapper per hook under.husky/(pre-commit,prepare-commit-msg,commit-msg,post-checkout,post-merge,pre-push). Each wrapper calls the correspondinggit-hook-<name>command (no internal hyphens, e.g.pre-commit->git-hook-precommit), passing all arguments through, and falls back to sourcing.husky/install.shifzz-useisn't onPATH:#!/bin/sh command -v zz-use > /dev/null 2>&1 || . "$(dirname "$0")/install.sh" zz-use -x git-hook-precommit "$@"
-
Husky's own init sets
core.hooksPath .husky/_- this feature doesn't touchgit configdirectly. -
With real hooks active, the
ai-codingfeature'slint-staged-precommit.shClaudePreToolUsehook steps aside, so lint-staged doesn't run twice per commit.
All hooks utilities are installed globally and can be configured in the package.json file.
A default configuration is provided for each utility, but you can override it by modifying the package.json file.
Unlike the other utilities, validate-branch-name's package.json config is not static: configure-validate-branch-name.sh (run automatically by feature-configure githooks) generates it from the git-flow branch/prefix scheme set up by the gitutils feature (gitflow.branch.master, gitflow.branch.develop, gitflow.prefix.feature, .bugfix, .release, .hotfix, .support), plus a main/develop/feature/bugfix/release/hotfix/support fallback when git-flow isn't configured. AI coding agent branch prefixes (copilot/, claude/) are always allowed too, overridable via the GITHOOKS_EXTRA_BRANCH_PREFIXES environment variable (comma-separated). Re-run feature-configure githooks after changing the git-flow config to regenerate the pattern.
The default lint-staged config formats JSON with json-normalize and everything else with prettier. Lockfiles are excluded from both globs (!(*schema|package-lock).json and a prettier glob that skips package-lock), because npm owns their formatting and the hook regenerates them whenever a package.json is staged. Earlier versions of this stub matched them, which rewrote the whole lockfile on any package.json commit.
feature-configure githooks merges this config into your package.json, and a JSON merge only adds keys. The two superseded globs are therefore removed by KEY lines in this feature's .clean, which needs a feature-configure from a tomgrv/scripts release that knows that directive; an older one prints Unknown .clean directive and skips the line. If that happens, delete these two keys from lint-staged in your package.json by hand:
!(*schema).json!(templates/**/*|.agents/**).{js,jsx,ts,tsx,md,html,css,vue,yaml,yml,json}
The following hooks are provided:
pre-commit- Executesgit-pre-committo validate the code before committing. It also runslint-stagedto lint and format staged files, ensuring code quality and consistency before changes are committed.prepare-commit-msg- Utilizescommitizento help prepare standardized and conventional commit messages, making it easier to follow commit message guidelines.commit-msg- Runscommitlintto validate commit messages against defined rules, ensuring that all commit messages are consistent and follow the project's conventions.post-merge- Handles changes inpackage.jsonandcomposer.jsonafter a merge, ensuring that dependencies are correctly updated and any necessary post-merge tasks are performed.post-checkout- Executesgit updateto synchronize the current branch with the latest changes from the remote repository, keeping the local branch up-to-date.pre-push- Runsvalidate-branch-nameto ensure that the branch name adheres to the project's naming conventions before pushing changes to the remote repository.
Consumer repos get these deployed workflows under .github/workflows/. Each has a job timeout and a concurrency group: PR checks cancel superseded runs, scheduled jobs queue.
validate-pr-format.yml— checks the PR title (commitlint + devmoji) and the PR source branch.validate-pr-secret.yml— scans the PR for secrets with gitleaks on open, reopen and every push; skipped whenGITLEAKS_LICENSEis not set.validate-pr-vulnerabilities.yml— scans the PR with Trivy (dependencies, IaC misconfigurations, secrets) when the PR is marked ready for review, and fails on fixable HIGH/CRITICAL findings and uploads the SARIF report to the Security tab; the action is pinned to a commit.update-labels.yml— weekly sync of.github/labels.jsonto the repository labels (needs onlyissues: write).clean-branches.yml— weekly deletion of branches whose pull request is closed.configure-github.yml— weekly restriction ofmainto the GitHub Actions bot.
The feature also includes the following VS Code customizations:
-
Extensions:
vivaxy.vscode-conventional-commitssoftwareape.rebasertomblind.scm-buttons-vscode
-
Settings:
conventionalCommits.gitmoji:false
If you have a feature that you would like to add to this repository, please open an issue or submit a pull request.