Skip to content

Commit 2a934e1

Browse files
authored
Check commit author/committer identity for AI attribution, not just message (#2455)
check_no_ai_attribution.sh only ever scanned commit message text. A recent PR carried a commit with a clean message but an AI-tool author identity, so it went undetected. Give the script an --identity mode for checking a "Name <email>" string, and wire it into commit-msg (via `git var GIT_AUTHOR_IDENT`/`GIT_COMMITTER_IDENT`, available before the commit exists) and into pre-push (scanning each pushed commit's author AND committer, since a bad identity can slip in via either field).
1 parent 9e50924 commit 2a934e1

3 files changed

Lines changed: 47 additions & 8 deletions

File tree

‎ci_scripts/check_no_ai_attribution.sh‎

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,35 @@
1414
# Usage:
1515
# check_no_ai_attribution.sh <file-with-commit-message> # commit-msg hook use
1616
# git log --format=%B -1 <sha> | check_no_ai_attribution.sh - # pre-push hook use
17+
# check_no_ai_attribution.sh --identity <file|-> # author/committer identity check
1718
#
1819
# Deliberately checks the commit message only, not file/diff content: a patch
1920
# can legitimately carry a real upstream contributor's byline (a person can be
2021
# named Claude), and this check has no way to tell that apart from an AI
2122
# attribution trailer. The message text is ours to write, so it has no such
2223
# ambiguity.
24+
#
25+
# --identity is different: it checks an author/committer "Name <email>" string
26+
# (e.g. `git var GIT_AUTHOR_IDENT`, or `git log --format='%an <%ae>'`), not a
27+
# message. There's no "real person happens to be named Claude" ambiguity to
28+
# worry about here - this repo's commit identity is always
29+
# `Ludovic Henry <git@ludovic.dev>`, so any identity matching the pattern is
30+
# an AI-tool identity, full stop (PR #2448 leaked a `Claude <noreply@anthropic.com>`
31+
# author this way, past the message-only check above).
2332

2433
set -eu
2534

2635
pattern='claude|anthropic'
2736

28-
file="${1:?usage: check_no_ai_attribution.sh <file|->}"
37+
mode="${1:?usage: check_no_ai_attribution.sh <file|-> | --identity <file|->}"
38+
if [ "$mode" = "--identity" ]; then
39+
field="identity"
40+
file="${2:?usage: check_no_ai_attribution.sh --identity <file|->}"
41+
else
42+
field="message"
43+
file="$mode"
44+
fi
45+
2946
if [ "$file" = "-" ]; then
3047
content="$(cat)"
3148
else
@@ -34,10 +51,17 @@ fi
3451

3552
hit="$(printf '%s\n' "$content" | grep -inE "$pattern" || true)"
3653
if [ -n "$hit" ]; then
37-
echo "check_no_ai_attribution: commit message mentions Claude/Anthropic - this project never attributes work to an AI tool." >&2
38-
echo " Offending line(s):" >&2
39-
echo "$hit" | sed 's/^/ /' >&2
40-
echo " Remove any Co-Authored-By/Claude-Session/\"Generated by\" trailer and rewrite the message, then retry." >&2
54+
if [ "$field" = "identity" ]; then
55+
echo "check_no_ai_attribution: commit author/committer identity mentions Claude/Anthropic - this project never commits under an AI-tool identity." >&2
56+
echo " Offending line(s):" >&2
57+
echo "$hit" | sed 's/^/ /' >&2
58+
echo " Set the commit identity to Ludovic Henry <git@ludovic.dev> and retry." >&2
59+
else
60+
echo "check_no_ai_attribution: commit message mentions Claude/Anthropic - this project never attributes work to an AI tool." >&2
61+
echo " Offending line(s):" >&2
62+
echo "$hit" | sed 's/^/ /' >&2
63+
echo " Remove any Co-Authored-By/Claude-Session/\"Generated by\" trailer and rewrite the message, then retry." >&2
64+
fi
4165
exit 1
4266
fi
4367

‎ci_scripts/git-hooks/commit-msg‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,11 @@
1010
# shared core.hooksPath) runs fine.
1111
common_dir="$(git rev-parse --path-format=absolute --git-common-dir)"
1212
main_toplevel="$(dirname "$common_dir")"
13-
exec "$main_toplevel/ci_scripts/check_no_ai_attribution.sh" "$1"
13+
checker="$main_toplevel/ci_scripts/check_no_ai_attribution.sh"
14+
15+
"$checker" "$1" || exit 1
16+
17+
# Also reject the identity the commit is about to be made under - available
18+
# even before the commit exists via `git var`.
19+
git var GIT_AUTHOR_IDENT | "$checker" --identity - || exit 1
20+
git var GIT_COMMITTER_IDENT | "$checker" --identity - || exit 1

‎ci_scripts/git-hooks/pre-push‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# catches a commit made before the hook was installed, an amend/rebase that
88
# reintroduced one, or a merge commit whose own message was never run through
99
# commit-msg. Scans every commit about to be pushed that the remote doesn't
10-
# already have.
10+
# already have - both its message and its author/committer identity.
1111

1212
set -eu
1313

@@ -45,7 +45,15 @@ while read -r local_ref local_sha remote_ref remote_sha; do
4545
if ! printf '%s' "$msg" | "$checker" - 2>/tmp/check_no_ai_attribution.$$; then
4646
echo "check_no_ai_attribution: blocked in commit $(git rev-parse --short "$commit") ($local_ref):" >&2
4747
cat /tmp/check_no_ai_attribution.$$ >&2
48-
rm -f /tmp/check_no_ai_attribution.$$
48+
status=1
49+
fi
50+
rm -f /tmp/check_no_ai_attribution.$$
51+
52+
# Author AND committer: a bad identity can slip in via either field.
53+
idents="$(git log --format='%an <%ae>|%cn <%ce>' -1 "$commit" | tr '|' '\n')"
54+
if ! printf '%s\n' "$idents" | "$checker" --identity - 2>/tmp/check_no_ai_attribution.$$; then
55+
echo "check_no_ai_attribution: blocked in commit $(git rev-parse --short "$commit") ($local_ref):" >&2
56+
cat /tmp/check_no_ai_attribution.$$ >&2
4957
status=1
5058
fi
5159
rm -f /tmp/check_no_ai_attribution.$$

0 commit comments

Comments
 (0)