hyperhive/scripts/check-attribution-trailers.sh
atlas 5be4210f51 check-attribution-trailers.sh: fix broken remote-membership test
The origin-membership check used `case " $remotes " in *" origin "*)`,
padding $remotes (git remote's newline-separated output) with spaces.
That only puts literal spaces around the first and last entries, so with
more than one remote the case never matches, and the loop's
`[ "$r" = "origin" ] && continue` then discards origin permanently.
Result: a tree with origin plus any second remote hard-failed even
though origin resolved fine (argus's repro: aaa-dummy + origin).

Replace with a real membership test: grep -qx over one entry per line.
2026-09-16 19:47:37 +02:00

105 lines
4.1 KiB
Shell
Executable file

#!/bin/sh
# Flags Co-Authored-By trailers with Claude/Anthropic attribution in PR commits.
# The hive convention in /knowledge/hive-rules.md forbids these trailers.
#
# Pattern is anchored to an actual git trailer line — `^[[:space:]]*co-authored-by:`
# — not any mention of the phrase, then requires the value name `claude`,
# `anthropic`, or `noreply@anthropic.com`. Matching is case-insensitive and
# per-line (git trailers are one `Key: value` per line) to cover variants like
# `Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>` while letting a
# commit subject/body that merely *discusses* the trailer (e.g. this script's
# own "add CI lint for Co-Authored-By/Claude/Anthropic trailers") pass.
#
# Scope is the PR's commits (BASE..HEAD), not entire history — old commits
# must not fail the build.
set -eu
trailer_re='^[[:space:]]*co-authored-by:.*(claude|anthropic|noreply@anthropic\.com)'
branch="${GITHUB_BASE_REF:-main}"
# Remote name varies by checkout — CI's actions/checkout@v3 names it
# `origin`, but an agent worktree renames its remote to `forge` (see the
# hive's subagent briefs). Hardcoding `origin/` here made the script
# unresolvable, and unresolvable, in the old code, silently read as clean
# (fetch-or-`true`, then a fallback to `HEAD~10`) — that fail-open was the
# actual bug. So: try `BASE_REF` if the caller set one, then every
# configured remote in a stable order (an `origin` remote first, to match
# CI, then the rest alphabetically), fetching a remote's copy of `branch`
# if it isn't already present locally. If none of that resolves a base,
# exit 1 with a clear message — never guess a commit range.
base=""
if [ -n "${BASE_REF:-}" ]; then
if git rev-parse --verify "$BASE_REF" >/dev/null 2>&1; then
base="$BASE_REF"
else
echo "check-attribution-trailers: BASE_REF=$BASE_REF does not resolve" >&2
exit 1
fi
else
remotes="$(git remote)"
ordered_remotes=""
if printf '%s\n' "$remotes" | grep -qx origin; then
ordered_remotes="origin"
fi
for r in $(printf '%s\n' "$remotes" | sort); do
[ "$r" = "origin" ] && continue
ordered_remotes="${ordered_remotes:+$ordered_remotes }$r"
done
for remote in $ordered_remotes; do
candidate="refs/remotes/${remote}/${branch}"
if ! git rev-parse --verify "$candidate" >/dev/null 2>&1; then
# actions/checkout@v3 fetches the PR head but may not fetch the base branch
git fetch "$remote" "$branch" 2>/dev/null || true
fi
if git rev-parse --verify "$candidate" >/dev/null 2>&1; then
base="$candidate"
break
fi
done
if [ -z "$base" ]; then
echo "check-attribution-trailers: base ref unresolvable — no configured remote has a '${branch}' ref (tried: ${remotes:-none}), refusing to guess a commit range" >&2
exit 1
fi
fi
# `|| true` here used to erase git log's own exit status, so a genuine
# failure (bad range, corrupt ref) and a merely-empty range read the same:
# both fell through to `-z "$commits"` and exited 0 "clean". Capture the
# status via the `if` guard instead — that's exempt from `set -e` on
# purpose — so a failure exits loudly and an empty-but-successful range
# still means "no commits, nothing to check".
if ! commits="$(git log --reverse --format='%H' "${base}..HEAD")"; then
echo "check-attribution-trailers: git log failed for range ${base}..HEAD — see error above" >&2
exit 1
fi
if [ -z "$commits" ]; then
exit 0
fi
hits=""
for sha in $commits; do
msg="$(git log -1 --format='%B' "$sha")"
if printf '%s\n' "$msg" | grep -qiE "$trailer_re"; then
subject="$(git log -1 --format='%s' "$sha")"
hits="${hits}${sha}|${subject}
"
fi
done
if [ -n "$hits" ]; then
printf '%s' "$hits" | while IFS='|' read -r sha subject; do
[ -z "$sha" ] && continue
short_sha="$(printf '%s' "$sha" | cut -c1-8)"
printf '::error title=Attribution trailer found in %s::%s — commit carries a Co-Authored-By trailer with Claude/Anthropic attribution, forbidden by /knowledge/hive-rules.md\n' \
"$short_sha" "$subject"
done
count="$(printf '%s' "$hits" | grep -c '|' || true)"
printf 'check-attribution-trailers: %s commit(s) with prohibited trailers found\n' "$count" >&2
exit 1
fi
exit 0