Skip to content

Commit 6624252

Browse files
committed
Intake gate: isolate per-PR failures on assignment; key assignment runs per assignee
Also reword the refused-reopen advice for PRs that passed via a maintainer override, and drop an unused test fixture. No-Verification-Needed: workflow script, its tests, and config only Signed-off-by: Max Isbey <224885523+maxisbey@users.noreply.github.com>
1 parent 1e7b8d9 commit 6624252

3 files changed

Lines changed: 28 additions & 6 deletions

File tree

.github/scripts/pr_intake_gate.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,17 @@ module.exports = async function run({ github, context, core }) {
3838
});
3939
const prs = closed.filter((i) => i.pull_request && closingRefs(i.body).includes(issueNumber));
4040
console.log(`#${issueNumber} assigned to ${assignee}: ${prs.length} gate-closed PR(s) reference it`);
41-
for (const pr of prs) await evaluate(pr.number, 'assigned', context.payload.sender?.login, issueNumber);
41+
// Evaluate each independently so one transient failure doesn't strand
42+
// the rest (this event won't fire again for the same assignment).
43+
const failures = [];
44+
for (const pr of prs) {
45+
try {
46+
await evaluate(pr.number, 'assigned', context.payload.sender?.login, issueNumber);
47+
} catch (e) {
48+
failures.push(`#${pr.number}: ${e.message}`);
49+
}
50+
}
51+
if (failures.length) throw new Error(`Could not re-evaluate ${failures.join('; ')}`);
4252
return;
4353
}
4454

@@ -150,7 +160,7 @@ module.exports = async function run({ github, context, core }) {
150160
MARKER,
151161
`This PR now passes the intake check (${reason}), but GitHub won't let it be reopened — usually because the branch was force-pushed or deleted while the PR was closed, or because another open PR uses the same branch.`,
152162
'',
153-
`If you have another open PR from this branch, please continue there. Otherwise, either push the branch back to \`${pr.head.sha.slice(0, 7)}\` and edit this PR's description to retry, or open a new PR with the same \`Fixes #<issue>\` line.`,
163+
`If you have another open PR from this branch, please continue there. Otherwise, either push the branch back to \`${pr.head.sha.slice(0, 7)}\` and edit this PR's description to retry, or open a new PR that links the same issue (if a maintainer had waved this one through, mention that so they can do the same there).`,
154164
].join('\n');
155165
}
156166

.github/scripts/pr_intake_gate.test.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ const PEOPLE = {
2121
maintainer: { type: 'User', perms: { admin: true, maintain: true, push: true, triage: true, pull: true } },
2222
triager: { type: 'User', perms: { triage: true, pull: true } }, // e.g. a trusted-contributors team
2323
outsider: { type: 'User', perms: { pull: true } },
24-
another: { type: 'User', perms: { pull: true } },
2524
'dependabot[bot]': { type: 'Bot', perms: {} },
2625
'somebot[bot]': { type: 'Bot', perms: {} },
2726
};
@@ -236,6 +235,19 @@ test('kill switch (ENFORCE=false) reaches the same verdicts but writes nothing',
236235
}
237236
});
238237

238+
test('on assignment, one PR failing to re-evaluate does not stop the others (run still fails)', async () => {
239+
const world = makeWorld({
240+
prs: [
241+
pr(3300, 'outsider', { state: 'closed', labels: [LABEL], body: 'Fixes #10', gateComment: true }),
242+
pr(3301, 'outsider', { state: 'closed', labels: [LABEL], body: 'Fixes #10', gateComment: true }),
243+
],
244+
issues: [issue(10, { assignees: ['outsider'] })],
245+
});
246+
world.failPullsGet = 3300;
247+
await assert.rejects(run(world, assigned(10, 'outsider', 'maintainer'), { enforce: true }), /Could not re-evaluate #3300/);
248+
assert.equal(world.prs.get(3301).state, 'open');
249+
});
250+
239251
test('an API error while checking permissions fails the run rather than closing the PR', async () => {
240252
const world = makeWorld({ prs: [pr(3300, 'outsider')] });
241253
world.failPermissionLookup = true;
@@ -297,7 +309,7 @@ function observe(world, expect) {
297309
// ── A tiny in-memory GitHub ────────────────────────────────────────────────
298310

299311
function makeWorld({ prs = [], issues = [] }) {
300-
const world = { prs: new Map(), issues: new Map(), writes: [], failPermissionLookup: false, nextCommentId: 100 };
312+
const world = { prs: new Map(), issues: new Map(), writes: [], failPermissionLookup: false, failPullsGet: null, nextCommentId: 100 };
301313
for (const p of prs) {
302314
const copy = structuredClone(p);
303315
copy.comments = copy.comments.map((c) => ({ id: world.nextCommentId++, ...c }));
@@ -326,7 +338,7 @@ function makeWorld({ prs = [], issues = [] }) {
326338
},
327339
},
328340
pulls: {
329-
get: async ({ pull_number }) => ({ data: apiPr(getPr(pull_number)) }),
341+
get: async ({ pull_number }) => { if (world.failPullsGet === pull_number) throw err(502, 'bad gateway'); return { data: apiPr(getPr(pull_number)) }; },
330342
update: async ({ pull_number, state }) => {
331343
const p = getPr(pull_number);
332344
write(`${state === 'open' ? 'reopen' : 'close'} #${pull_number}`);

.github/workflows/require-linked-issue.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
runs-on: ubuntu-latest
6262
timeout-minutes: 10
6363
concurrency:
64-
group: require-linked-issue-${{ github.event.pull_request.number || inputs.pr_number || format('issue-{0}', github.event.issue.number) }}
64+
group: require-linked-issue-${{ github.event.pull_request.number || inputs.pr_number || format('issue-{0}-{1}', github.event.issue.number, github.event.assignee.login) }}
6565
cancel-in-progress: false
6666
permissions:
6767
contents: read # check out the gate script from the default branch

0 commit comments

Comments
 (0)