diff --git a/.github/workflows/commit-trailers.yml b/.github/workflows/commit-trailers.yml new file mode 100644 index 0000000..8c355f1 --- /dev/null +++ b/.github/workflows/commit-trailers.yml @@ -0,0 +1,65 @@ +name: commit-trailers +on: + pull_request: + merge_group: + +permissions: + contents: read + pull-requests: read + +jobs: + no-ai-coauthor: + runs-on: ubuntu-latest + steps: + - name: Reject AI Co-Authored-By trailers + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + if (!pr) return; + const commits = await github.paginate(github.rest.pulls.listCommits, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + }); + // Known AI coding agents that stamp a `Co-Authored-By:` trailer, + // matched by their vendor email or distinctive bot login. We key on + // bot identities (unambiguous), never on bare human names, so + // legitimate human co-authors are never blocked. + const AI_AGENTS = new RegExp([ + 'anthropic\\.com', // Claude / Claude Code + 'copilot-swe-agent', // GitHub Copilot + '\\+copilot@users\\.noreply', // GitHub Copilot + 'devin-ai-integration', // Devin (Cognition) + 'cursoragent|@cursor\\.com', // Cursor Agent + 'google-labs-jules', // Google Jules / Gemini + '@aider\\.chat', // Aider + 'windsurf|codeium', // Windsurf / Codeium + ].join('|'), 'i'); + const coAuthorLine = /^\s*co-authored-by:.*$/gim; + const offenders = commits + .filter((c) => + (c.commit.message.match(coAuthorLine) || []).some((l) => + AI_AGENTS.test(l), + ), + ) + .map((c) => c.sha.slice(0, 8)); + if (offenders.length === 0) { + core.info('No AI Co-Authored-By trailers found.'); + return; + } + const msg = [ + `Commits ${offenders.join(', ')} carry a "Co-Authored-By: " trailer.`, + '', + 'Under US copyright law (which governs CNCF projects) a Co-Authored-By', + 'trailer naming an AI agent can attribute rights over the code to its', + 'vendor. Use an informational "Assisted-By: " trailer instead — it', + 'discloses AI assistance without transferring authorship.', + '', + 'Rationale: https://github.com/anthropics/claude-code/issues/66602', + '', + 'Fix: rebase and amend the trailer, then force-push.', + 'Prevent it at source: set includeCoAuthoredBy: false in your Claude Code settings.', + ].join('\n'); + await core.summary.addRaw(msg).write(); + core.setFailed(msg);