One canonical home per artifact. No per-repo duplication of hook definitions or shared lint configs.Across the workspace, dozens of repos had their own
.pre-commit-config.yaml with the same baseline hooks rewritten everywhere, plus shared lint configs (.markdownlint, .tflint.hcl, .ansible-lint, .yamllint) copy-pasted across multiple repos. The shared pre-commit architecture eliminates that duplication so a single source of truth drives every consumer.
Canonical homes
Six profiles
Pick one per consumer repo.base, nix, and markdown are deliberately identical modules; the name signals intent to the reader.
Consumer pattern — Nix path
Consumer pattern — non-Nix path
Why one canonical home per artifact
- Single update propagates everywhere.
nix flake updatein the consumer (or in nix-devenv for cross-org propagation) pulls every hook to a new pinned version. No per-reporev:bumps. (Non-Nix consumers instead get Renovaterev:bumps, scheduled and auto-merged by trust tier — see Dependency automation.) - Drift dies. Before consolidation the inventory found
pre-commit-terraformpinned at six different revs across the workspace andmarkdownlint-cliat four. Canonical pinning eliminates that. - New repo onboarding is one line.
nix flake init -t github:dryvist/nix-devenv#with-hooksplus picking a profile, no copy-paste of 30 lines of YAML.
What stays per-repo
The architecture explicitly does NOT centralise everything. Hooks with low coverage in the inventory stay opt-in per repo because they have high false-positive rates or dominate the hook cycle:checkov(terraform security) — appears in 3 inventory repos; opt-in viapre-commit.settings.hooks.checkov.enable = true;bandit(Python security) — appears in 1 repodetect-secrets— appears in 1 repo- AWS / GCP / Azure
tflintplugins — repo-targeting; canonicaltflint.hclenables only the core terraform plugin
Rules
- Don’t add hook definitions to a consumer-repo
.pre-commit-config.yaml. If the canonical profile doesn’t cover something, add it tonix-devenv’s base profile or the matching language profile, then pull it through everywhere on the nextnix flake update. - Don’t duplicate shared lint config files (
.markdownlint,.tflint.hcl,.ansible-lint,.yamllint). Pull them via the Nix path (fetch-shared-configs) or copy at scaffold fromdryvist/.github(non-Nix path). - A clean migration PR adds
flake.nix+flake.lock, modifies.envrc, deletes.pre-commit-config.yaml, and deletes the duplicated lint config files that the canonical now covers.
pre-commit-architecture skill (claude-code-plugins, git-workflows plugin), which loads when an agent edits pre-commit config or scaffolds hooks.
Known limitations
cachix/git-hooks.nix’s built-intflintwrapper drops args beyond$1, so theterraformprofile’s--config <sharedConfigs.tflint>plumbing doesn’t reach tflint. Consumers either keep a synced copy of the canonical.tflint.hclin the repo (tflint’s local-config discovery finds it) or overridetflint.argstolib.mkForce [ ].terraform-validatehooks need network access totofu initexternal modules.nix flake checkruns hooks in a sandboxed environment without network. Repos with external module references overrideterraform-validate.enable = lib.mkForce falsefor the flake-check path and runtofu init -backend=falseplustofu validatein CI.gitleaksis not incachix/git-hooks.nix’s built-in hook set yet. Consumers wire it as a custom hook locally; a follow-up adds it to the base profile.
References
dryvist/nix-devenv— Nix-path canonicaldryvist/.githubprecommit/README.md— non-Nix-path canonical + architecture rationaledryvist/claude-code-pluginspre-commit-architectureskill — on-demand rule for AI agents- cachix/git-hooks.nix — upstream hook framework
- flake-parts — Nix flake composition framework