From 84bfb9496375801924b7c8381364076578a5bbda Mon Sep 17 00:00:00 2001 From: nate stephany Date: Tue, 17 Feb 2026 14:15:25 +0100 Subject: [PATCH 1/6] touch From 8f1670f3c114dbacda330b2621dd7081537ac090 Mon Sep 17 00:00:00 2001 From: nate stephany Date: Tue, 17 Feb 2026 14:47:31 +0100 Subject: [PATCH 2/6] perm-test: Use Commit Status API to avoid duplicate check run conflicts --- .github/workflows/merge-gate.yml | 34 +++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/.github/workflows/merge-gate.yml b/.github/workflows/merge-gate.yml index 7290a70..7553520 100644 --- a/.github/workflows/merge-gate.yml +++ b/.github/workflows/merge-gate.yml @@ -9,12 +9,13 @@ on: jobs: merge-gate: - name: Maintainer Approval Required + name: merge-gate runs-on: ubuntu-latest permissions: pull-requests: read + statuses: write steps: - - name: Require maintainer approval + - name: Check maintainer approval uses: actions/github-script@v7 with: script: | @@ -62,13 +63,32 @@ jobs: } } + // Use the Commit Status API instead of core.setFailed() + // This ensures multiple runs for the same commit overwrite + // each other instead of creating conflicting check runs if (validApprovals.length === 0) { - let message = `❌ Requires approval from a user with Maintain or Admin permission before merging.\n` + - `Contributors (Write permission) cannot satisfy this requirement.`; + let description = 'Requires approval from a Maintain/Admin user'; if (staleApprovals.length > 0) { - message += `\n\n⚠️ Stale approvals (new commits pushed since review): ${staleApprovals.join(', ')}`; + description = `Stale approvals (pushed after review): ${staleApprovals.join(', ')}`; } - core.setFailed(message); + await github.rest.repos.createCommitStatus({ + owner, + repo, + sha: currentHead, + state: 'failure', + context: 'Maintainer Approval Required', + description, + }); + core.info(`❌ ${description}`); } else { - core.info(`✅ Approved by maintainer(s): ${validApprovals.join(', ')}`); + const approvedBy = validApprovals.join(', '); + await github.rest.repos.createCommitStatus({ + owner, + repo, + sha: currentHead, + state: 'success', + context: 'Maintainer Approval Required', + description: `Approved by: ${approvedBy}`, + }); + core.info(`✅ Approved by maintainer(s): ${approvedBy}`); } From b8430025b98bdc4e8e5cb5bdb76562b743bd88eb Mon Sep 17 00:00:00 2001 From: nate stephany Date: Tue, 17 Feb 2026 15:01:39 +0100 Subject: [PATCH 3/6] touch From f499980559aded5b2ed48b4bc332ec346ad7e22d Mon Sep 17 00:00:00 2001 From: nate stephany Date: Tue, 17 Feb 2026 15:09:32 +0100 Subject: [PATCH 4/6] Test stale approval detection From 67d9f0a54fb9f009095ed1ad2ac1996b923d4e22 Mon Sep 17 00:00:00 2001 From: nate stephany Date: Tue, 17 Feb 2026 15:11:47 +0100 Subject: [PATCH 5/6] perm-test: Use timestamp comparison for stale review detection --- .github/workflows/merge-gate.yml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/merge-gate.yml b/.github/workflows/merge-gate.yml index 7553520..462bcbe 100644 --- a/.github/workflows/merge-gate.yml +++ b/.github/workflows/merge-gate.yml @@ -25,6 +25,16 @@ jobs: const prAuthor = context.payload.pull_request.user.login; const currentHead = context.payload.pull_request.head.sha; + // Get the latest commit's timestamp to detect stale reviews + // GitHub updates review.commit_id to current HEAD, making it + // unreliable for staleness checks. Timestamps are immutable. + const { data: headCommit } = await github.rest.repos.getCommit({ + owner, + repo, + ref: currentHead, + }); + const headCommitDate = new Date(headCommit.commit.committer.date); + // Get all reviews on this PR const { data: reviews } = await github.rest.pulls.listReviews({ owner, @@ -39,15 +49,16 @@ jobs: } // Check each approver's repo permission level - // Only count approvals against the current HEAD commit + // Only count approvals submitted AFTER the latest commit const validApprovals = []; const staleApprovals = []; for (const [login, review] of latestReviews) { if (review.state !== 'APPROVED') continue; if (login === prAuthor) continue; - // Reject approvals made against an older commit - if (review.commit_id !== currentHead) { + // Reject approvals submitted before the latest commit + const reviewDate = new Date(review.submitted_at); + if (reviewDate < headCommitDate) { staleApprovals.push(login); continue; } From 7b248981e02d0a36667c29088cc9c763b0aac54f Mon Sep 17 00:00:00 2001 From: nate stephany Date: Tue, 17 Feb 2026 15:27:57 +0100 Subject: [PATCH 6/6] Test stale approval after timestamp fix