[PATCH githooks] New hook to detect AI-assisted commits

Michał Górny <[email protected]> Wed, 17 Jun 2026 13:43:38 +0200
Newsgroups gmane.linux.gentoo.devel
Message-ID <[email protected]>
Add a hook that specifically reject comments using common LLM
signatures, including `Co-authored-by:`, `Assisted-by:`
and `Generated-by:` trailers.  This is meant to prevent the worst
accidents, and it is by no means complete or bulletproof.  Some
of the patterns are zealous because tools like Gemini do not reliably
use a single e-mail address; we can add exceptions if they actually hit
real people (and we get clear confirmation that their employer doesn't
force them using LLMs on us).

Signed-off-by: Michał Górny <[email protected]>
---
 local/tests/update-07-ai-policy.sh |  74 ++++++++++++++++++
 local/update-07-ai-policy          | 117 +++++++++++++++++++++++++++++
 2 files changed, 191 insertions(+)
 create mode 100755 local/tests/update-07-ai-policy.sh
 create mode 100755 local/update-07-ai-policy

diff --git a/local/tests/update-07-ai-policy.sh b/local/tests/update-07-ai-policy.sh
new file mode 100755
index 0000000..59649c1
--- /dev/null
+++ b/local/tests/update-07-ai-policy.sh
@@ -0,0 +1,74 @@
+#!/bin/bash
+# gentoo-infra: infra/githooks.git:tests/update-07-ai-policy.sh
+# Tests for update-07-ai-policy
+# Copyright 2018-2026 Michał Górny and others
+# Distributed under the terms of the GNU General Public License v2 or later
+
+. "${BASH_SOURCE%/*}"/lib.sh
+HOOK_PATH=${BASH_SOURCE%/*}/../update-07-ai-policy
+[[ ${HOOK_PATH} == /* ]] || HOOK_PATH=${PWD}/${HOOK_PATH}
+
+# Error message patterns
+FAIL_LLM="*: suspicious of LLM use
+*"
+
+export [email protected]
+export GIT_COMMITTER_NAME='Non A. Dev'
+export GIT_COMMITTER_EMAIL=${GL_USER}
+export GIT_AUTHOR_NAME=${GIT_COMMITTER_NAME}
+export GIT_AUTHOR_EMAIL=${GIT_COMMITTER_EMAIL}
+
+tbegin "Commit without LLM authorship"
+git commit --allow-empty -m "A commit
+
+Co-authored-by: Jean Claude <[email protected]>" -q
+test_success
+
+tbegin "Commit using Assisted-by: tag"
+git commit --allow-empty -m "A commit
+
+Assisted-by: Some model" -q
+test_failure "${FAIL_LLM}"
+
+AUTHORS=(
+	[email protected]
+	[email protected]
+	[email protected]
+	[email protected]
+	"12345+claude[bot]@users.noreply.github.com"
+	[email protected]
+	[email protected]
+	[email protected]
+	"12345+chatgpt-codex-connector[bot]@users.noreply.github.com"
+	[email protected]
+	[email protected]
+	[email protected]
+	[email protected]
+	[email protected]
+	[email protected]
+	[email protected]
+	[email protected]
+	[email protected]
+)
+
+einfo "Author:"
+eindent
+for AUTHOR in "${AUTHORS[@]}"; do
+	tbegin "<${AUTHOR}>"
+	git commit --author="foo <${AUTHOR}>" --allow-empty -m "A commit" -q
+	test_failure "${FAIL_LLM}"
+done
+eoutdent
+
+einfo "Co-authored-by:"
+eindent
+for AUTHOR in "${AUTHORS[@]}"; do
+	tbegin "<${AUTHOR}>"
+	git commit --allow-empty -m "A commit
+
+Co-authored-by: foo <${AUTHOR}>" -q
+	test_failure "${FAIL_LLM}"
+done
+eoutdent
+
+exit "${TEST_RET}"
diff --git a/local/update-07-ai-policy b/local/update-07-ai-policy
new file mode 100755
index 0000000..91c5d62
--- /dev/null
+++ b/local/update-07-ai-policy
@@ -0,0 +1,117 @@
+#!/bin/bash
+# gentoo-infra: infra/githooks.git:update-07-ai-policy
+# Safety check for slop.
+# Copyright 2018-2026 Michał Górny and others
+# Distributed under the terms of the GNU General Public License v2 or later
+
+# Disable filename expansion
+set -f
+# Force UTF-8
+export LC_CTYPE=en_US.UTF-8
+# Make == case-insensitive
+shopt -s nocasematch
+
+# --- Command line
+refname=${1}
+oldrev=${2}
+newrev=${3}
+
+# --- Safety check
+if [[ -z ${GIT_DIR} ]]; then
+	echo "Don't run this script from the command line." >&2
+	echo " (if you want, you could supply GIT_DIR then run" >&2
+	echo "  ${0} <ref> <oldrev> <newrev>)" >&2
+	exit 1
+fi
+
+if [[ -z ${refname} || -z ${oldrev} || -z ${newrev} ]]; then
+	echo "usage: ${0} <ref> <oldrev> <newrev>" >&2
+	exit 1
+fi
+
+ret=0
+
+# special cases
+zeros=0000000000000000000000000000000000000000
+# branch removal
+[[ ${newrev} == "${zeros}" ]] && exit 0
+rev_list_arg="${oldrev}..${newrev}"
+exec 10</dev/null
+# new branch; check all commits that are reachable from ${newrev}
+# but not reachable from any existing ref in refs/
+if [[ ${oldrev} == "${zeros}" ]]; then
+	rev_list_arg="${newrev}"
+	exec 10< <(git rev-parse --not --exclude="${refname}" --all)
+fi
+
+while read -r commithash; do
+	found=no
+	while read -r line; do
+		case ${line} in
+			# Determined by searching commit messages on GitHub.
+			# Note that some patterns are overzealous because of lack
+			# of consistency upstream.  We can tighten them if need ever
+			# arises.
+
+			co-authored-by:*\<[email protected]\>*)
+				;&
+			# Claude Code uses noreply@ or claude@
+			co-authored-by:*\<*@anthropic.com\>*)
+				;&
+			# apparently a variety of github usernames can also be used
+			co-authored-by:*\<[email protected]\>*)
+				;&
+			co-authored-by:*\<*+claude\[bot\]@users.noreply.github.com\>*)
+				;&
+			# Codex uses noreply@ or codex@, could also be ChatGPT
+			co-authored-by:*\<*@openai.com\>*)
+				;&
+			# seriously?
+			co-authored-by:*\<*+chatgpt-codex-connector\[bot\]@users.noreply.github.com\>*)
+				;&
+			# Cursor uses cursoragent@
+			co-authored-by:*\<*@cursor.com\>*)
+				;&
+			# Grok uses grok@
+			co-authored-by:*\<*@x.ai\>*)
+				;&
+			# Gemini uses gemini@, gemini-bot@, gemini-cli@, noreply@...
+			co-authored-by:*\<*@google.com\>*)
+				;&
+			# more random LLMs
+			co-authored-by:*\<*@paperclip.ing\>*)
+				;&
+			co-authored-by:*\<*@sisyphuslabs.ai\>*)
+				;&
+			co-authored-by:*\<*@sourcegraph.com\>*)
+				;&
+
+			# kernel.org policy
+			# (technically, these could be used with non-LLMs; adjust
+			# if someone wants to use them)
+			assisted-by:*)
+				found=yes
+				;;
+			generated-by:*)
+				found=yes
+				;;
+		esac
+	done < <(
+		# turn the author into Co-authored-by to make matching easier
+		git show -q --pretty=format:'Co-authored-by: <%ae>%n%b' "${commithash}"
+	)
+
+	if [[ ${found} == yes ]]; then
+		echo "${commithash}: suspicious of LLM use"
+		ret=1
+	fi
+done < <(git rev-list "${rev_list_arg}" --stdin <&10)
+
+if [[ ${ret} == 1 ]]; then
+	echo
+	echo "Please make sure to read the AI policy:"
+	echo "  https://wiki.gentoo.org/wiki/Project:Council/AI_policy"
+fi
+
+# --- Finished
+exit "${ret}"