Skip to content

Commit 5835cb5

Browse files
authored
Merge pull request #155 from MZC-CSC/propose/ai-attribution-check-workflow
ci: add AI direct commit attribution check workflow
2 parents ffade96 + e85f895 commit 5835cb5

1 file changed

Lines changed: 145 additions & 0 deletions

File tree

.github/workflows/no-ai-trace.yml

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# ------------------------------------------------------------
2+
# Policy enforcement for open-source contributions: block AI direct commit signoff and co-authored-by attribution from being merged.
3+
#
4+
# In open-source contributions, the human author is expected to create commits and open pull requests. Under current open-source license frameworks (DCO, Sign-off, CLA, etc.) AI-attributed commits may have unclear implications. Following the community proposal from 2026-05-08, until the project policy is finalized, AI direct commit signoff and co-authored-by records are prohibited.
5+
#
6+
# Canonical source: https://github.com/MZC-CSC/cmig-workflow/blob/main/conf/workflow-templates/no-ai-trace.yml
7+
# Reference policy: https://github.com/MZC-CSC/cmig-workflow/blob/main/POLICY-AI-TRACE-GUARD.md
8+
# Local path in target repo: .github/workflows/no-ai-trace.yml
9+
# ------------------------------------------------------------
10+
11+
name: AI Direct Commit Attribution Check
12+
13+
on:
14+
pull_request:
15+
branches: [main, develop, master]
16+
push:
17+
branches: [main, develop, master]
18+
19+
permissions:
20+
contents: read
21+
pull-requests: write
22+
23+
jobs:
24+
block-ai-attribution:
25+
name: Block AI direct commit signoff and co-authored-by
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: actions/checkout@v6
29+
with:
30+
fetch-depth: 0
31+
32+
- name: Determine commit range to scan
33+
id: range
34+
run: |
35+
if [ "${{ github.event_name }}" = "pull_request" ]; then
36+
echo "from=${{ github.event.pull_request.base.sha }}" >> $GITHUB_OUTPUT
37+
echo "to=${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT
38+
else
39+
BEFORE="${{ github.event.before }}"
40+
if [ "$BEFORE" = "0000000000000000000000000000000000000000" ] || [ -z "$BEFORE" ]; then
41+
echo "from=${{ github.sha }}~1" >> $GITHUB_OUTPUT
42+
else
43+
echo "from=$BEFORE" >> $GITHUB_OUTPUT
44+
fi
45+
echo "to=${{ github.sha }}" >> $GITHUB_OUTPUT
46+
fi
47+
48+
- name: Scan commits for AI direct attribution
49+
id: scan
50+
run: |
51+
FROM="${{ steps.range.outputs.from }}"
52+
TO="${{ steps.range.outputs.to }}"
53+
54+
# Patterns matched against commit message body and trailers
55+
BODY_PATTERNS='Co-Authored-By:.*[Cc]laude|Co-Authored-By:.*[Aa]nthropic|Co-Authored-By:.*[Cc]opilot|Generated-By:.*[Cc]laude|Generated-By:.*[Cc]ode|Assisted-By|Co-Developed-By|noreply@anthropic\.com'
56+
57+
# Patterns matched against author and committer email
58+
EMAIL_PATTERNS='@anthropic\.com|copilot-swe-agent'
59+
60+
FOUND=""
61+
for sha in $(git rev-list "$FROM..$TO" 2>/dev/null); do
62+
SUBJECT=$(git log -1 --pretty='%s' "$sha")
63+
BODY=$(git log -1 --pretty='%B' "$sha")
64+
AUTHOR_EMAIL=$(git log -1 --pretty='%ae' "$sha")
65+
COMMITTER_EMAIL=$(git log -1 --pretty='%ce' "$sha")
66+
67+
BAD=""
68+
HIT=$(echo "$BODY" | grep -iE "$BODY_PATTERNS" | head -3)
69+
if [ -n "$HIT" ]; then
70+
BAD="$BAD"$'\n'" - body/trailer match:"$'\n'"$(echo "$HIT" | sed 's/^/ /')"
71+
fi
72+
73+
for email in "$AUTHOR_EMAIL" "$COMMITTER_EMAIL"; do
74+
if echo "$email" | grep -iqE "$EMAIL_PATTERNS"; then
75+
BAD="$BAD"$'\n'" - AI tool email match: $email"
76+
fi
77+
done
78+
79+
if [ -n "$BAD" ]; then
80+
FOUND="$FOUND"$'\n\n'"### $sha"$'\n'" subject: $SUBJECT$BAD"
81+
fi
82+
done
83+
84+
if [ -n "$FOUND" ]; then
85+
{
86+
echo "found=true"
87+
echo "details<<EOF"
88+
echo "$FOUND"
89+
echo "EOF"
90+
} >> $GITHUB_OUTPUT
91+
92+
echo "::error::AI direct commit signoff or co-authored-by attribution detected. See logs and PR comment for details."
93+
echo "$FOUND"
94+
exit 1
95+
fi
96+
97+
echo "found=false" >> $GITHUB_OUTPUT
98+
echo "OK: no AI direct commit signoff or co-authored-by attribution found."
99+
100+
- name: Post failure comment on PR
101+
if: failure() && github.event_name == 'pull_request'
102+
uses: actions/github-script@v9
103+
with:
104+
script: |
105+
const details = `${{ steps.scan.outputs.details }}`;
106+
const body = [
107+
'## AI Direct Commit Attribution Blocked',
108+
'',
109+
'This pull request cannot be merged because it contains commits with AI tool attribution (Claude, Anthropic, GitHub Copilot, etc.) in commit message trailers (for example `Co-Authored-By`, `Signed-off-by`, `Generated-By`) or in the author/committer email.',
110+
'',
111+
'### Why this is blocked',
112+
'',
113+
'In open-source contributions, the human author is expected to create commits and open pull requests. Under current open-source license frameworks (DCO, Sign-off, CLA, etc.) AI-attributed commits may have unclear implications. Following the community proposal from 2026-05-08, until the project policy is finalized, **AI direct commit signoff and co-authored-by records are prohibited** in this repository.',
114+
'',
115+
'<details><summary>Matched commits</summary>',
116+
'',
117+
'```',
118+
details,
119+
'```',
120+
'',
121+
'</details>',
122+
'',
123+
'### How to fix',
124+
'',
125+
'Most recent commit only - strip the attribution trailers:',
126+
'```bash',
127+
'git commit --amend --message="$(git log -1 --pretty=\'%s%n%n%b\' | grep -vE \'Co-Authored-By:|Generated-By:|Assisted-By:|Co-Developed-By:\')"',
128+
'git push --force-with-lease',
129+
'```',
130+
'',
131+
'Multiple commits - use interactive rebase:',
132+
'```bash',
133+
'git rebase -i HEAD~5',
134+
'# Mark each commit as `reword` and remove the trailer lines in the editor',
135+
'git push --force-with-lease',
136+
'```',
137+
'',
138+
'The check will re-run automatically after the force-push, and the merge can proceed once the AI direct commit signoff and co-authored-by records are removed. Repository administrators may also enforce this check via Branch Protection rules.'
139+
].join('\n');
140+
github.rest.issues.createComment({
141+
issue_number: context.issue.number,
142+
owner: context.repo.owner,
143+
repo: context.repo.repo,
144+
body: body
145+
});

0 commit comments

Comments
 (0)