From 3659b5acc215785d7bc7529c78b8431f573aae8f Mon Sep 17 00:00:00 2001 From: bircni Date: Fri, 5 Jun 2026 23:33:40 +0200 Subject: [PATCH] ci(workflows): add AgentScan workflow to flag possible AI-assisted PRs (#37962) This PR adds an automated AgentScan workflow to help detect and handle pull requests that appear to be created or authored primarily by automated agents. - If a PR is classified as `automation` or community-flagged, the workflow: - Adds the `possible bot` label, - Posts a policy comment linking to the repository AI Contribution Policy (`CONTRIBUTING.md#ai-contribution-policy`) and listing required disclosures and checks, - Optionally closes the PR if classification indicates an automated/unwelcome submission. --- .github/workflows/agent-scan.yml | 113 +++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 .github/workflows/agent-scan.yml diff --git a/.github/workflows/agent-scan.yml b/.github/workflows/agent-scan.yml new file mode 100644 index 00000000000..2c84face622 --- /dev/null +++ b/.github/workflows/agent-scan.yml @@ -0,0 +1,113 @@ +name: AgentScan + +on: + # jobs only use pinned actions and never checkout code + pull_request_target: # zizmor: ignore[dangerous-triggers] + types: [opened, reopened, synchronize, edited] + +concurrency: + group: agent-scan-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + issues: write + pull-requests: write + +jobs: + agentscan: + runs-on: ubuntu-latest + steps: + - name: AgentScan + id: agentscan + uses: MatteoGabriele/agentscan-action@0a0c88109b5153dff2805f969f5060441efb7b65 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + skip-members: "dependabot[bot],renovate[bot], giteabot (backports)" + agent-scan-comment: false + + - name: Handle flagged PR + if: contains(fromJSON('["automation","mixed"]'), steps.agentscan.outputs.classification) || steps.agentscan.outputs.community-flagged == 'true' + env: + CLASSIFICATION: ${{ steps.agentscan.outputs.classification }} + COMMUNITY_FLAGGED: ${{ steps.agentscan.outputs.community-flagged }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 + with: + script: | + const core = require('@actions/core'); + const prNumber = context.payload.pull_request.number; + const classification = process.env.CLASSIFICATION; + const communityFlagged = process.env.COMMUNITY_FLAGGED === 'true'; + const shouldClose = classification === 'automation' || communityFlagged; + + const issue = context.payload.pull_request; + const labels = issue.labels?.map(l => l.name) || []; + + if (!labels.includes('possible bot')) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + labels: ['possible bot'], + }); + } + + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 100, + }); + + const alreadyCommented = comments.some(c => c.user.type === 'Bot' && c.body.includes('AI Contribution Policy')); + + if (!alreadyCommented) { + const closingNote = shouldClose + ? "We're closing this for now as the account looks automated. If we got that wrong, please just reopen the PR and we'll take another look." + : 'If this was flagged in error, we apologise! 😳 Just let us know. 🙏'; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: [ + "We've flagged this pull request as potentially AI-assisted.", + '', + 'Gitea welcomes the thoughtful use of AI tools, but contributors must use them responsibly and clearly disclose any assistance. Please follow the AI Contribution Policy in `CONTRIBUTING.md` and update this PR accordingly:', + '', + 'Maintainers may close PRs that do not disclose AI assistance, appear to be low-quality AI-generated content, or where the contributor cannot explain the changes.', + '', + 'See: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#ai-contribution-policy', + '', + closingNote, + ].join('\n'), + }); + } else { + core.info('Possible-bot comment already exists - skipping comment.'); + } + + if (shouldClose && issue.state === 'open' && !alreadyCommented) { + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + state: 'closed', + title: '🚨 unwelcome pr from bot 🚨', + }); + } + + const actionTaken = [ + 'Added `possible bot` label', + alreadyCommented ? null : 'posted policy comment', + shouldClose && !alreadyCommented ? 'closed PR' : null, + ].filter(Boolean).join(', '); + + core.summary + .addHeading('AgentScan: Possible Bot Flag', 2) + .addTable([ + [{ data: 'Property', header: true }, { data: 'Value', header: true }], + ['Pull Request', `#${prNumber}`], + ['Classification', classification], + ['Community flagged', String(communityFlagged)], + ['Action', actionTaken || 'No action (already handled)'], + ]) + .write();