Skip to content

feat(auth): add hardened test-only service-token fallback with strict… #154

feat(auth): add hardened test-only service-token fallback with strict…

feat(auth): add hardened test-only service-token fallback with strict… #154

Workflow file for this run

name: GC Auto Approve

Check failure on line 1 in .github/workflows/gc-auto-approve.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/gc-auto-approve.yml

Invalid workflow file

(Line: 16, Col: 9): Unrecognized named-value: 'secrets'. Located at position 1 within expression: secrets.GC_PAT != ''
on:
workflow_run:
workflows: ["Code Quality", "Build and Push F1 API", "CodeQL"]
types: [completed]
permissions:
contents: read
checks: read
pull-requests: write
jobs:
approve-pr:
runs-on: ubuntu-latest
if: ${{ secrets.GC_PAT != '' }}
steps:
- name: GC Approval
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GC_PAT }}
script: |
const run = context.payload.workflow_run;
if (run.event !== 'pull_request') {
core.info(`Skipping: workflow_run event is '${run.event}', not 'pull_request'.`);
return;
}
if (!run.pull_requests || run.pull_requests.length === 0) {
core.info('Skipping: no pull request found on workflow_run payload.');
return;
}
const pullNumber = run.pull_requests[0].number;
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pullNumber
});
if (pr.data.state !== 'open') {
core.info(`Skipping: PR #${pullNumber} is not open.`);
return;
}
const headSha = pr.data.head.sha;
const authUser = await github.rest.users.getAuthenticated();
const reviewerLogin = authUser.data.login;
const existingReviews = await github.paginate(github.rest.pulls.listReviews, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pullNumber,
per_page: 100
});
const hasApprovalForHead = existingReviews.some((review) =>
review.user?.login === reviewerLogin &&
review.state === 'APPROVED' &&
review.commit_id === headSha
);
if (hasApprovalForHead) {
core.info(`Skipping: ${reviewerLogin} already approved head commit ${headSha}.`);
return;
}
const checkRuns = await github.paginate(github.rest.checks.listForRef, {
owner: context.repo.owner,
repo: context.repo.repo,
ref: headSha,
per_page: 100
});
// Exclude this workflow's own check run to avoid self-blocking while in progress.
const relevantRuns = checkRuns.filter((check) => !check.name.startsWith('GC Auto Approve /'));
if (relevantRuns.length === 0) {
core.info('Skipping: no relevant check runs found for PR head commit.');
return;
}
const notCompleted = relevantRuns.filter((check) => check.status !== 'completed');
if (notCompleted.length > 0) {
core.info(`Skipping: waiting for checks to complete: ${notCompleted.map((c) => c.name).join(', ')}`);
return;
}
const invalidConclusions = relevantRuns.filter(
(check) => check.conclusion !== 'success' && check.conclusion !== 'skipped'
);
if (invalidConclusions.length > 0) {
core.info(
`Skipping: checks not in success/skipped state: ${invalidConclusions
.map((c) => `${c.name}=${c.conclusion}`)
.join(', ')}`
);
return;
}
const unresolvedQuery = `
query($owner: String!, $repo: String!, $pullNumber: Int!, $cursor: String) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pullNumber) {
reviewThreads(first: 100, after: $cursor) {
nodes {
isResolved
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
let unresolvedThreadCount = 0;
let cursor = null;
do {
const response = await github.graphql(unresolvedQuery, {
owner: context.repo.owner,
repo: context.repo.repo,
pullNumber,
cursor
});
const threads = response.repository.pullRequest.reviewThreads;
unresolvedThreadCount += threads.nodes.filter((thread) => !thread.isResolved).length;
cursor = threads.pageInfo.hasNextPage ? threads.pageInfo.endCursor : null;
} while (cursor);
if (unresolvedThreadCount > 0) {
core.info(`Skipping: unresolved review threads: ${unresolvedThreadCount}`);
return;
}
const verifiedChecks = relevantRuns.map((check) => ({
name: check.name,
conclusion: check.conclusion
}));
const body = [
'GC Agent Auto-Review',
'',
'Verified checks:',
...verifiedChecks.map((check) => `- ${check.name}: ${check.conclusion}`),
'- All review comments resolved',
'',
'```json',
JSON.stringify(
{
decision: 'APPROVE',
verifiedChecks,
unresolvedReviewThreads: unresolvedThreadCount,
headSha
},
null,
2
),
'```'
].join('\n');
await github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pullNumber,
event: 'APPROVE',
body
});