From 1f6dc84f68b4631e77ebc11a452cb0b03eecde57 Mon Sep 17 00:00:00 2001 From: sommerfeld Date: Tue, 19 May 2026 16:45:17 +0100 Subject: feat(git): commit-msg hook strips AI Co-authored-by trailers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Various agentic tools (Copilot CLI, VS Code chat, etc.) auto-append `Co-authored-by: Copilot <...>` / Claude / Codex trailers, which then trip the pre-push hook's agent-coauthor check and force a manual amend before the push goes through. Scrub at commit time instead. Uses the same agent-substring list as executable_pre-push (kept in sync by comment). Triggered as commit-msg (not pre-commit — pre-commit runs before the message exists). Drops matching trailers in-place, collapses trailing blanks, and is a no-op otherwise. Also symlinks the new hook in the remote-dev home-manager config so it deploys on the Ubuntu VM. Bypass: `git commit --no-verify`. --- dot_config/git/hooks/executable_commit-msg | 60 ++++++++++++++++++++++++++++++ remote-dev/home.nix | 1 + 2 files changed, 61 insertions(+) create mode 100755 dot_config/git/hooks/executable_commit-msg diff --git a/dot_config/git/hooks/executable_commit-msg b/dot_config/git/hooks/executable_commit-msg new file mode 100755 index 0000000..78dba63 --- /dev/null +++ b/dot_config/git/hooks/executable_commit-msg @@ -0,0 +1,60 @@ +#!/bin/sh +# Strip Co-authored-by trailers whose identity looks like a coding agent +# (Copilot, Claude, Codex, ChatGPT, Cursor, Aider, Devin, ...). Various +# tools — GitHub Copilot CLI, VS Code chat, etc. — append these +# automatically and they then trip the pre-push hook's agent check. +# Easier to scrub at commit time than to amend later. +# +# Activated via core.hooksPath in ~/.config/git/config so it applies to +# every repo unless that repo overrides hooksPath itself. +# +# Bypass: git commit --no-verify + +set -eu + +msg_file=$1 + +# Keep this list in sync with executable_pre-push. +agent_subs='copilot claude codex chatgpt cursor aider devin [bot] @openai. @anthropic.' + +# Rewrite the message file, dropping any Co-authored-by line whose +# lowercased content contains an agent substring. We deliberately +# operate line-by-line rather than on trailer blocks because the file +# already includes git's auto-generated comment block (`# ...`) and we +# don't want to touch any of that. +tmp=$(mktemp) +trap 'rm -f "$tmp"' EXIT INT TERM + +awk -v agent_subs="$agent_subs" ' + BEGIN { n = split(agent_subs, subs, " ") } + function is_agent(s, i) { + for (i = 1; i <= n; i++) if (index(s, subs[i])) return 1 + return 0 + } + { + # Match the trailer case-insensitively but keep the original + # casing for everything we pass through. + lower = tolower($0) + if (lower ~ /^co-authored-by:[[:space:]]/ && is_agent(lower)) { + stripped = 1 + next + } + print + } + END { exit stripped ? 10 : 0 } +' "$msg_file" >"$tmp" && rc=0 || rc=$? + +# awk exits 10 only when we actually dropped a line; anything else is +# treated as a real error. +case $rc in + 0) exit 0 ;; + 10) ;; + *) printf 'commit-msg: awk failed (rc=%d); leaving message untouched.\n' "$rc" >&2; exit 0 ;; +esac + +# Collapse any trailing blank lines produced by the removal so we don't +# leave a dangling blank trailer block. +sed -e :a -e '/^$/{$d;N;ba' -e '}' "$tmp" >"$msg_file" + +printf 'commit-msg: stripped AI Co-authored-by trailer(s).\n' >&2 +exit 0 diff --git a/remote-dev/home.nix b/remote-dev/home.nix index 926aadb..5dc55d0 100644 --- a/remote-dev/home.nix +++ b/remote-dev/home.nix @@ -161,6 +161,7 @@ in # so map each hook to its stripped name explicitly. The executable bit # comes from the working-tree file mode (git resolves the symlink). "git/hooks/pre-push".source = link "dot_config/git/hooks/executable_pre-push"; + "git/hooks/commit-msg".source = link "dot_config/git/hooks/executable_commit-msg"; }; # ── Rootless podman config ────────────────────────────────────────────────── -- cgit v1.3.1