feat(guardrails): opt-in git commit-msg hook for tool-agnostic subject enforcement - #1077
Conversation
…t enforcement Closes #919 (audit umbrella #912, f1 depth layer / f4 backstop). New installable git hook template lib/git-hooks/commit-msg-convention.sh + /guardrails:setup apply install-commit-msg action (explicit opt-in; bare apply still writes nothing). The hook validates every commit's subject in the repo against the team-tracked pattern via a copy of the shared enforcement resolver — editor commits, -F <file>, IDEs, humans outside Claude. Trust-surface contract per the issue: chain-or-refuse (managed repos refused with manager-side remediation; existing commit-msg hooks chained as commit-msg.pre-guardrails with final rejection authority, never overwritten — including the operator's machine-local gate); sentinel-marked for convention-inference exclusion (echo-cycle guard); personal .git/hooks lane only — core.hooksPath and tracked files never touched (team lane is a human PR decision, and hooksPath changes are the shape block-no-verify refuses); unresolved = no enforcement, resolver removed = fail open; fixup!/squash!/amend! exempt; rejection message instructs fixing the subject and never suggests --no-verify (the designed exit from the block-no-verify deadlock). 15-case contract suite; guardrails 0.11.0 -> 0.12.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Warning Automated review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
Re-running the job, or pushing a new commit, will retry the review. |
|
Warning Automated security review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
Re-running the job, or pushing a new commit, will retry the review. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6ecfb7eac
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| **Install (on a clean preflight):** copy `${CLAUDE_PLUGIN_ROOT}/lib/git-hooks/commit-msg-convention.sh` | ||
| to `<git-dir>/hooks/commit-msg` and `${CLAUDE_PLUGIN_ROOT}/hooks/resolve-convention-pattern.sh` | ||
| to `<git-dir>/hooks/guardrails-resolve-convention.sh` (resolve `<git-dir>` via | ||
| `git rev-parse --absolute-git-dir` — in a worktree `.git` is a file), `chmod +x` both. |
There was a problem hiding this comment.
Install hooks at Git’s resolved hook path
When this setup is run from a linked worktree, git rev-parse --absolute-git-dir resolves to the per-worktree admin dir (for example .git/worktrees/<name>), but Git resolves hooks through git rev-parse --git-path hooks/commit-msg, which points at the common hooks directory. I verified a commit in a linked worktree ignores a hook copied under the absolute git dir, so apply install-commit-msg can report success while normal commits in that worktree remain unenforced; use Git’s --git-path hooks/... result for the install/chain targets instead.
Useful? React with 👍 / 👎.
| [[ -n "$line" ]] || continue | ||
| [[ "${line:0:1}" == "$comment_char" ]] && continue |
There was a problem hiding this comment.
Validate comment-prefixed commit subjects
For commits whose real subject begins with the configured comment character, this skips that subject as if it were template commentary; git commit -m '# bad' (and git commit -F file) preserve that line as the commit subject, with cleanup controlled separately (git commit -h lists -m/-F message inputs and a separate --cleanup option). With a ticket-pattern config I verified git commit -m '# definitely invalid' is accepted and recorded, and a -F file whose first line is invalid #... but later line matches is also accepted, so the new backstop can be bypassed by a comment-prefixed subject.
Useful? React with 👍 / 👎.
Summary
Audit f1's depth layer / f4's backstop (#912): a git
commit-msghook enforcing the team-tracked subject pattern regardless of tool or shell — editor commits,git commit -F <file>, IDE integrations, humans outside Claude — where the Claude-Code-layer gates see nothing.New installable template
lib/git-hooks/commit-msg-convention.sh+/guardrails:setup apply install-commit-msg(explicit opt-in; bareapplystill writes nothing):core.hooksPath, lefthook, husky, pre-commit) → refused with the manager-side remediation. An existingcommit-msghook is never overwritten: chained ascommit-msg.pre-guardrails(runs first, its rejection is final) or the install refuses. Covers the operator's machine-local commit-msg gate.guardrails-commit-msg-convention) so convention-inference tooling excludes the installed hook as a signal — it is derived FROM the tracked config; counting it would echo-cycle. Sentinel re-install is idempotent..git/hooks/lane only.core.hooksPath, hook-manager configs, and tracked files are never touched — the committed team lane is a human PR decision, andcore.hooksPathchanges are exactly the shapeblock-no-verifyrefuses (interaction resolved by not going there).fixup!/squash!/amend!exempt (autosquash).--no-verify(whichblock-no-verifyrefuses in-session anyway); in Claude sessions the CC-layer gate (feat(guardrails): CC-layer content gate — commit subject + PR title #914/PR feat(guardrails): CC-layer content gate — commit subject + PR title #1071) blocks first, making this hook the cross-tool backstop.Reads the same resolver contract as the CC-layer gate (copied beside the hook at install time — an installed consumer-repo hook cannot participate in the sync seam, so it carries its own unedited copy).
guardrails
0.11.0→0.12.0with CHANGELOG entry.Test plan
lib/git-hooks/commit-msg-convention.test.sh— unresolved/PCRE pass-through, enforce block/allow, comment-line skipping, autosquash exemptions, empty message, resolver-removed fail-open, chain rejection-final + pass-through, sentinel presencescripts/check-changed-skills.sh main— setup skill PASS (0 errors)scripts/check-changelog-parity.sh --check-bump main— passRelated
🤖 Generated with Claude Code