From bf8e309b19757cb82d19b9589591c38b27bb7004 Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 15 Sep 2026 22:58:55 +0200 Subject: [PATCH] ops: add CI lint for Co-Authored-By/Claude/Anthropic trailers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three builder runs tonight carried a Co-Authored-By: Claude trailer despite bold instructions forbidding it. A rule in a prompt is not a control — this gate is. Flags commits in a PR's range carrying co-authored-by + (claude OR anthropic OR noreply@anthropic.com), case-insensitive, covering all observed spellings. Wired into CI (new attribution-trailer job) and pre-push hook. Mirrors existing lint scripts in structure and exit conventions. Pattern tested against all three variants plus a normal commit. --- .forgejo/workflows/ci.yml | 17 ++++++ scripts/check-attribution-trailers.sh | 85 +++++++++++++++++++++++++++ scripts/pre-push | 6 ++ 3 files changed, 108 insertions(+) create mode 100755 scripts/check-attribution-trailers.sh diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 60535005..c109794c 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -68,6 +68,23 @@ jobs: # their own. See scripts/check-doc-refs.sh. run: sh scripts/check-doc-refs.sh + attribution-trailers: + name: attribution-trailer lint + runs-on: [hive-ci] + # Pure git — seconds. + timeout-minutes: 5 + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: lint + # Flags Co-Authored-By trailers with Claude/Anthropic attribution + # in PR commits (hive convention in /knowledge/hive-rules.md forbids + # them — an attribution line in a prompt is not a control). Own + # job, and IS a required check on the forge (branch protection) — a + # hit blocks merge. See scripts/check-attribution-trailers.sh. + run: sh scripts/check-attribution-trailers.sh + prose-lint: name: prose lint (vale) runs-on: [hive-ci] diff --git a/scripts/check-attribution-trailers.sh b/scripts/check-attribution-trailers.sh new file mode 100755 index 00000000..4d589421 --- /dev/null +++ b/scripts/check-attribution-trailers.sh @@ -0,0 +1,85 @@ +#!/bin/sh +# CI lint: flags Co-Authored-By trailers with Claude/Anthropic attribution in +# PR commits. The hive convention in /knowledge/hive-rules.md forbids these +# trailers — an attribution line in a prompt is not a control. This gate is. +# +# Emits a CI error annotation per offending commit and exits 1 if any commit +# in the PR's range carries a prohibited trailer. Runs as its own CI job and +# IS a required check on the forge (branch protection) — a hit blocks merge. +# +# Pattern: case-insensitive `co-authored-by` followed by `claude`, `anthropic`, +# or `noreply@anthropic.com` anywhere in the line. Covers variants seen in the +# wild: `Co-Authored-By: Claude`, `Co-Authored-By: Claude Sonnet 4.5 `. +# +# Scope: commits in the PR's range (BASE..HEAD), not the entire history — old +# commits must not fail the build. In CI, BASE is the target branch +# (GITHUB_BASE_REF / github.event.pull_request.base.ref in GH Actions; similar +# vars in Forgejo). Falls back to origin/main for local runs. +set -eu + +# Determine base ref: in CI (pull_request event), use the PR's base branch; +# locally, default to origin/main. CI env vars: GITHUB_BASE_REF (GitHub +# Actions), similar for Forgejo/Gitea (not standardized across runners, so we +# check both common patterns). +if [ -n "${GITHUB_BASE_REF:-}" ]; then + # GitHub Actions / Forgejo Actions pull_request context + base="origin/${GITHUB_BASE_REF}" +elif [ -n "${CI_COMMIT_REF_NAME:-}" ]; then + # GitLab-style variable (some Forgejo setups) + base="origin/main" +else + # Local run or unknown CI — default to origin/main + base="origin/main" +fi + +# Ensure the base ref exists (in CI, actions/checkout@v3 fetches the PR head +# but may not fetch the base branch by default — fetch it if missing). +if ! git rev-parse --verify "$base" >/dev/null 2>&1; then + # Extract branch name and fetch it + branch="${base#origin/}" + git fetch origin "$branch" 2>/dev/null || true + # If still missing, fall back to main + if ! git rev-parse --verify "$base" >/dev/null 2>&1; then + base="origin/main" + git fetch origin main 2>/dev/null || base="HEAD~10" + fi +fi + +# Get all commits in the PR's range. `--reverse` lists oldest first (clearer +# for annotation output). `--format=%H` gets the full commit sha. +commits="$(git log --reverse --format='%H' "${base}..HEAD" 2>/dev/null || true)" + +if [ -z "$commits" ]; then + # No commits in range — either not on a PR branch, or nothing new. Pass. + exit 0 +fi + +# Check each commit's full message for the prohibited trailer pattern. +# Case-insensitive grep for `co-authored-by` + any of `claude`, `anthropic`, +# `noreply@anthropic.com`. +hits="" +for sha in $commits; do + msg="$(git log -1 --format='%B' "$sha")" + # Match case-insensitive co-authored-by AND (claude OR anthropic OR noreply@anthropic.com) + # Use grep -E with -i for case-insensitive matching + if printf '%s\n' "$msg" | grep -qiE 'co-authored-by.*claude|co-authored-by.*anthropic|co-authored-by.*noreply@anthropic\.com'; then + # Found a hit — build annotation + 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 diff --git a/scripts/pre-push b/scripts/pre-push index 62634252..6e812400 100755 --- a/scripts/pre-push +++ b/scripts/pre-push @@ -34,5 +34,11 @@ if ! sh "$repo_root/scripts/check-doc-refs.sh"; then exit 1 fi +echo "pre-push: running attribution-trailer lint..." >&2 +if ! sh "$repo_root/scripts/check-attribution-trailers.sh"; then + echo "pre-push: attribution-trailer lint FAILED — fix before pushing" >&2 + exit 1 +fi + echo "pre-push: lints passed ✓" >&2 exit 0