#!/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 # shellcheck source=./_dispatch.sh . "${0%/*}/_dispatch.sh" dispatch_repo_hook commit-msg "$@" 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