Skip to content

chore(sandcastle): review hub SHA update to 8a0a288 #36

chore(sandcastle): review hub SHA update to 8a0a288

chore(sandcastle): review hub SHA update to 8a0a288 #36

name: Check Attribution
# No branch filter: the base branch is read at runtime, so this works the same
# on repos that call it main and repos that call it master.
on: pull_request
# Coding agents sign their work either with a Co-Authored-By trailer or by
# claiming the author/committer field. Squash-merging collects the trailers of
# every commit in the PR into the merge commit, so a single unnoticed trailer
# reaches the default branch permanently. Each identity below is a
# vendor-controlled address or a known agent's GitHub App handle. Dependency
# bots (dependabot, renovate) are deliberately allowed: they are maintenance
# automation, not authorship claims.
env:
# Bare identities, matched against a trailer address and against the
# author/committer fields. The optional digits cover GitHub's
# "<id>+<handle>@users.noreply.github.com" form, whose id varies per app.
AI_IDENTITY_RE: '(noreply@anthropic\.com|cursoragent@cursor\.com|(noreply|codex)@openai\.com|noreply@aider\.chat|openhands@all-hands\.dev|noreply@opencode\.ai|noreply@continue\.dev|clio-agent@sisyphuslabs\.ai|roomote@roocode\.com|copilot@github\.com|([0-9]+[+])?(Copilot|gemini-code-assist\[bot\]|greptile-apps\[bot\]|coderabbitai\[bot\]|ellipsis-dev\[bot\]|qodo-merge-pro\[bot\]|sweep-ai\[bot\]|bito-code-review\[bot\]|roomote\[bot\]|factory-droid\[bot\]|opencode-agent\[bot\]|google-labs-jules\[bot\])@users\.noreply\.github\.com)'
jobs:
check-bot-coauthors:
if: ${{ !endsWith(github.actor, '[bot]') }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
- name: Check for bot Co-Authored-By lines
env:
BASE_REF: ${{ github.base_ref }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
COMMITS=$(git log --format='%H' "origin/${BASE_REF}..${HEAD_SHA}" 2>/dev/null || true)
if [ -z "$COMMITS" ]; then
echo "No commits to check"
exit 0
fi
# Anchored to a full trailer line so a mention in prose does not fail.
TRAILER_RE="^[[:space:]]*co-authored-by:[[:space:]]+.*<${AI_IDENTITY_RE}>[[:space:]]*$"
FOUND=0
for sha in $COMMITS; do
MSG=$(git log --format='%B' -1 "$sha")
HITS=$(printf '%s\n' "$MSG" | grep -Ei "$TRAILER_RE" || true)
if [ -n "$HITS" ]; then
echo "::error::Commit $sha contains a bot Co-Authored-By line. Remove it with: git rebase -i and edit the commit message."
printf '%s\n' "$HITS"
FOUND=1
fi
done
if [ "$FOUND" -eq 1 ]; then
echo ""
echo "To fix: run 'git rebase -i origin/${BASE_REF}', change 'pick' to 'reword' for flagged commits, then remove the Co-Authored-By lines."
exit 1
fi
echo "All commits clean."
- name: Check for bot commit authors
env:
BASE_REF: ${{ github.base_ref }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
# Some agents (Copilot's cloud agent, Jules, Devin) claim the author
# or committer field instead of adding a trailer, so the check above
# cannot see them. Written to a file rather than piped so the loop
# runs in this shell and its result survives.
git log --format='%H %ae %ce' "origin/${BASE_REF}..${HEAD_SHA}" > /tmp/authors.txt 2>/dev/null || true
FOUND=0
while read -r sha author committer; do
[ -z "$sha" ] && continue
for addr in "$author" "$committer"; do
if printf '%s\n' "$addr" | grep -Eqi "^${AI_IDENTITY_RE}$"; then
echo "::error::Commit $sha is authored or committed by a coding agent: $addr"
FOUND=1
fi
done
done < /tmp/authors.txt
if [ "$FOUND" -eq 1 ]; then
echo ""
echo "To fix: rewrite the commit with 'git rebase -i origin/${BASE_REF}', then 'git commit --amend --author=\"Your Name <you@example.com>\"'."
exit 1
fi
echo "All commit authors clean."