diff --git a/.github/workflows/security-review.yml b/.github/workflows/security-review.yml index 3c79a3cee08..0ffc017102d 100644 --- a/.github/workflows/security-review.yml +++ b/.github/workflows/security-review.yml @@ -7,6 +7,7 @@ on: permissions: contents: read pull-requests: write + issues: write id-token: write jobs: @@ -31,40 +32,48 @@ jobs: Read that file first, then follow its instructions exactly. Review only the changes introduced by this PR. Post your findings as a structured review comment. - claude_args: | - --max-turns 50 - --model claude-opus-4-6 + claude_args: --max-turns 50 --model claude-opus-4-7 - - name: Post fallback comment on failure - if: steps.claude-review.outcome == 'failure' + - name: Post status comment if Claude did not comment + if: always() uses: actions/github-script@v7 with: script: | - // Check if there's already a sticky comment from Claude const { data: comments } = await github.rest.issues.listComments({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, }); + const outcome = '${{ steps.claude-review.outcome }}'; + // Claude posts its own sticky comment when successful; only intervene if it didn't. const hasClaudeComment = comments.some(c => - c.body && c.body.includes('Security Review') + c.body && c.body.includes('Security Review') && c.user.type === 'Bot' ); if (!hasClaudeComment) { + const isFailure = outcome === 'failure' || outcome === 'cancelled'; await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: [ - '## Security Review', - '', - '⚠️ **Automated security review did not complete.**', - '', - 'Claude hit the max-turns limit or encountered an error before posting findings.', - 'A manual review of S0 (project-scoped data access), S1 (authorization policies),', - 'and S2 (audit trail coverage) is recommended for this PR.', - '', - 'See the [workflow run](https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions/runs/' + context.runId + ') for details.', - ].join('\n'), + body: isFailure + ? [ + '## Security Review', + '', + '⚠️ **Automated security review did not complete.**', + '', + 'Claude hit the max-turns limit or encountered an error before posting findings.', + 'A manual review of S0 (project-scoped data access), S1 (authorization policies),', + 'and S2 (audit trail coverage) is recommended for this PR.', + '', + 'See the [workflow run](https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions/runs/' + context.runId + ') for details.', + ].join('\n') + : [ + '## Security Review', + '', + '⚠️ The review completed but no findings comment was posted.', + '', + 'See the [workflow run](https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions/runs/' + context.runId + ') for the raw Claude output.', + ].join('\n'), }); }