Skip to content

Commit 23f14db

Browse files
committed
Do not re-judge a merged pull request for the Owner/Reviewer lines
The body check added for #943 ran again on every comment, because enqueue-guard.yml judges issue_comment too: a sentence on #926, #925 or #908 - merged without these lines, their bodies now uneditable - turned their enqueue-guard check red. guard() takes pull.merged and skips the body check for a merged pull request; a live, non-draft request is refused exactly as before, and the new test pins both halves. Reviewer's INC at 87b4996. Read the two lines that name the owner and reviewer, and refuse a bot account's Verdict #943, both halves, and both were rules nothing checked. Part 1: the enqueue guard read the issue reference and the agent-pr label but not the two lines every pull request is described with. #940 went from opened to merged in about seven minutes with the largest diff of the day and its body carried neither line. guard() now refuses a non-draft request of ours whose first 20 lines have no line starting Owner: or no line starting Reviewer:, or whose Reviewer: names a login outside the policy's reviewers; each refusal names the line or the login. Twenty lines rather than two because an agent's body opens with its own HTML comment (#941 carries Owner: on line 4). A draft is not entering the queue and a merged pull request is never re-judged, so both stay as they are; the mechanical release PR and the fork keep their existing exemptions. Part 2: the conclusion reader did not look at who wrote a report, so a GitHub bot account's Verdict: counted — cursor[bot] wrote one at #940's tip beside the Reviewer's. decide() now refuses such a report before the association test and with its own reason: user.type is Bot, or the login ends [bot]. The rule is the account type and not a login list, because every Bot writes as the shared Birfy account (a User) and a list containing it would void the whole pipeline; what voids such a report is the real Reviewer's Correction from Reviewer:, the same shape as the #940 correction. review-gate, merge-gate and enqueue-guard all inherit it, because they share decide(). Docs: AGENTS.md and docs/autonomous-dev.md state both rules in one place each. Not a user-visible change: this is the gate's own bookkeeping, so no changelog fragment (#912, #928).
1 parent c728658 commit 23f14db

8 files changed

Lines changed: 220 additions & 17 deletions

File tree

‎.github/scripts/enqueue-guard.mjs‎

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,37 @@ const RECORD_ONLY = /^[\s>*+-]*(?:record only\s*[—–-]+\s*do not (?:merge|que
5050
// The rules only hold where the branch is ours: a fork is exempt, because we cannot push to its head
5151
// and the owner keeps their own workflow there (#711/#713).
5252
const ISSUE_REF = /^[\s>#-]*(?:fixes|closes)\s*(?:[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)?#\d+/im
53+
// #943: the body says who owns the pull request and who reviews it. `Owner:`/`Reviewer:` were a
54+
// sentence in AGENTS.md that nothing read: #940 went from opened to merged in about seven minutes with
55+
// the largest diff of the day, and its body carried neither line.
56+
//
57+
// The first 20 lines, not the first two: an agent's body opens with its own HTML comment (the open #941
58+
// carries `Owner:` on line 4), and a check that only read the top would refuse every one of them.
59+
const HEADER_LINES = 20
60+
const OWNER_LINE = /^\s*Owner:/i
61+
const REVIEWER_LINE = /^\s*Reviewer:/i
5362
const short = (s) => String(s ?? '').slice(0, SHORT)
5463
const labelNames = (labels) => (labels ?? []).map((l) => String(typeof l === 'string' ? l : l?.name ?? '').toLowerCase())
5564

65+
// The refusal names the line, not just the defect, so the fix is "add this line" rather than "something
66+
// about the body". A `Reviewer:` line with no value names nobody, which is the same defect as a missing
67+
// line: the queue is meant to know who reviews, not to see the word. A value is one login, with or
68+
// without a leading `@`, compared case-insensitively against the policy's `reviewers`.
69+
function headerReasons(body, policy) {
70+
const lines = String(body ?? '').split(/\r?\n/).slice(0, HEADER_LINES)
71+
const reasons = []
72+
if (!lines.some((line) => OWNER_LINE.test(line))) reasons.push('missing line: Owner:')
73+
const reviewer = lines.find((line) => REVIEWER_LINE.test(line))
74+
if (!reviewer) reasons.push('missing line: Reviewer:')
75+
else {
76+
const names = reviewer.replace(REVIEWER_LINE, '').split(',').map((value) => value.trim().replace(/^@/, '').toLowerCase()).filter(Boolean)
77+
if (!names.length) reasons.push('missing line: Reviewer:')
78+
const reviewers = new Set((policy?.reviewers ?? []).map((l) => String(l).toLowerCase()))
79+
for (const name of names) if (!reviewers.has(name)) reasons.push(`Reviewer names "${name}", who is not in reviewers`)
80+
}
81+
return reasons
82+
}
83+
5684
// The newest conclusion that names this head, if it carries the record-only marker. Only a body that
5785
// parses as a conclusion counts, so ordinary prose quoting the phrase is no marker; only the newest
5886
// one counts, so a fresh PASS at the same head supersedes an older parked report.
@@ -69,7 +97,7 @@ export function recordOnlyAt(reports, head) {
6997
}
7098

7199
// The decision, without the network: `result` is merge-gate's `judge` output for this head.
72-
export function guard({ pr, head, labels, body, mechanicalRelease, fork, result, reports }) {
100+
export function guard({ pr, head, labels, body, mechanicalRelease, fork, draft, merged, result, reports, policy }) {
73101
const quiet = recordOnlyAt(reports, result.head ?? head)
74102
const reasons = [...result.reasons]
75103
if (quiet) reasons.push('the newest conclusion at this head says "record only — do not merge"')
@@ -81,6 +109,11 @@ export function guard({ pr, head, labels, body, mechanicalRelease, fork, result,
81109
if (!mechanicalRelease && !fork) {
82110
if (!ISSUE_REF.test(String(body ?? ''))) reasons.push('no issue reference in the pull request body: add "Fixes #<n>" or "Fixes owner/repo#<n>"')
83111
if (!labelNames(labels).includes('agent-pr')) reasons.push('missing label: agent-pr')
112+
// #943: only a request to enter the queue. A draft is not entering it, and a merged pull request is
113+
// never re-judged: this check runs again on every comment (enqueue-guard.yml), so without the second
114+
// half a sentence on an old merged pull request would turn its check red over a body nobody can edit
115+
// any more (#926, #925, #908 and others merged without these lines).
116+
if (!draft && !merged) reasons.push(...headerReasons(body, policy))
84117
}
85118
const ok = result.ok && !quiet && reasons.length === 0
86119
return {
@@ -90,6 +123,8 @@ export function guard({ pr, head, labels, body, mechanicalRelease, fork, result,
90123
recordOnly: Boolean(quiet),
91124
mechanicalRelease: Boolean(mechanicalRelease),
92125
fork: Boolean(fork),
126+
draft: Boolean(draft),
127+
merged: Boolean(merged),
93128
labels: labels ?? [],
94129
// One loud sentence: a refusal says what to do ("do not enqueue") and why, so it cannot be read
95130
// as a warning that some other step may override.
@@ -126,11 +161,12 @@ export function decidePull({ gh = realGh, env = process.env, repo: repoOption, p
126161
const labels = (pull.labels ?? []).map((l) => l.name)
127162
// #676: the association travels with the report, the way review-gate.mjs reads the same two APIs. Without
128163
// it every conclusion is refused as "not tied to this repository", so a PASS could never clear the guard.
164+
// #943: the account type travels too, for the same reason — a bot account writes no conclusion.
129165
const reports = [
130-
...list(`repos/${repo}/issues/${pr}/comments?per_page=100`).map((c) => ({ kind: 'comment', login: c.user?.login, body: c.body, at: c.created_at, association: c.author_association })),
166+
...list(`repos/${repo}/issues/${pr}/comments?per_page=100`).map((c) => ({ kind: 'comment', login: c.user?.login, type: c.user?.type, body: c.body, at: c.created_at, association: c.author_association })),
131167
...list(`repos/${repo}/pulls/${pr}/reviews?per_page=100`)
132168
.filter((v) => (v.body ?? '').trim())
133-
.map((v) => ({ kind: 'review', login: v.user?.login, body: v.body, at: v.submitted_at, association: v.author_association })),
169+
.map((v) => ({ kind: 'review', login: v.user?.login, type: v.user?.type, body: v.body, at: v.submitted_at, association: v.author_association })),
134170
]
135171
const files = list(`repos/${repo}/pulls/${pr}/files?per_page=100`)
136172

@@ -144,7 +180,7 @@ export function decidePull({ gh = realGh, env = process.env, repo: repoOption, p
144180
// which is not ours either. Both sides are lower-cased because the repository name in the API paths is
145181
// case-insensitive: `--repo thinkflowlab/flybot` used to read our own branch as a fork and print ALLOW.
146182
const fork = String(pull.head?.repo?.full_name ?? '').toLowerCase() !== String(repo).toLowerCase()
147-
const decision = guard({ pr, head, labels, body: pull.body, mechanicalRelease, fork, result, reports })
183+
const decision = guard({ pr, head, labels, body: pull.body, mechanicalRelease, fork, draft: pull.draft, merged: pull.merged, result, reports, policy })
148184
return { decision, pull, repo, pr }
149185
}
150186

‎.github/scripts/enqueue-guard.test.mjs‎

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ const OLD = '175328d0c1b2a3d4e5f60718293a4b5c6d7e8f90'
1212
const HOST_FILE = 'core/src/host/turns.js' // T2: needs a PASS
1313
const TEST_FILE = 'core/test/enqueue-guard.test.js' // T1: needs none
1414

15+
// #943: a pull request that may enter the queue says who owns it and who reviews it, in its body.
16+
const HEADERS = 'Owner: Dev\nReviewer: reviewer-agent\n'
17+
1518
const policyFile = () => {
1619
const path = join(mkdtempSync(join(tmpdir(), 'enqueue-guard-policy-')), 'review-policy.json')
1720
writeFileSync(path, JSON.stringify({ reviewers: ['reviewer-agent', 'Birfy'], selfReview: ['Birfy'], writers: ['reviewer-agent', 'Reviewer', 'Birfy'] }))
@@ -29,7 +32,7 @@ const report = ({ tip = HEAD, verdict = 'PASS', extra = [] } = {}) =>
2932
const comment = (body = report(), at = '2026-10-21T10:31:00Z') =>
3033
({ user: { login: 'reviewer-agent' }, body, created_at: at, author_association: 'MEMBER' })
3134

32-
const fakeGh = ({ body = 'Fixes #602\n', comments = [], reviews = [], labels = ['agent-pr'], files = [], head = HEAD, author = 'opener', headRepo = 'ThinkFlowLab/FlyBot', permission = undefined } = {}) => {
35+
const fakeGh = ({ body = HEADERS + 'Fixes #602\n', comments = [], reviews = [], labels = ['agent-pr'], files = [], head = HEAD, author = 'opener', headRepo = 'ThinkFlowLab/FlyBot', permission = undefined, draft = false, merged = false } = {}) => {
3336
const calls = []
3437
const gh = (...args) => {
3538
const path = args[args.length - 1]
@@ -41,7 +44,7 @@ const fakeGh = ({ body = 'Fixes #602\n', comments = [], reviews = [], labels = [
4144
if (path.includes('/files')) return files.map((f) => JSON.stringify(typeof f === 'string' ? { filename: f } : f)).join('\n')
4245
if (path.includes('/comments')) return comments.map((c) => JSON.stringify(c)).join('\n')
4346
if (path.includes('/reviews')) return reviews.map((r) => JSON.stringify(r)).join('\n')
44-
if (/\/pulls\/\d+$/.test(path)) return JSON.stringify({ head: { sha: head, repo: { full_name: headRepo } }, user: { login: author }, body, labels: labels.map((name) => ({ name })) })
47+
if (/\/pulls\/\d+$/.test(path)) return JSON.stringify({ head: { sha: head, repo: { full_name: headRepo } }, user: { login: author }, body, labels: labels.map((name) => ({ name })), draft, merged })
4548
// Anything else — `gh pr merge` above all — is the guard doing more than checking.
4649
throw new Error('fake gh: unexpected ' + path)
4750
}
@@ -190,7 +193,7 @@ test('#718: a pull request without the agent-pr label is not enqueued', () => {
190193
})
191194

192195
test('#718: a Closes reference and the agent-pr label let it through', () => {
193-
const gh = fakeGh({ body: 'Closes #714\n\nthe detail', labels: ['agent-pr'], files: [HOST_FILE], comments: [comment()] })
196+
const gh = fakeGh({ body: HEADERS + 'Closes #714\n\nthe detail', labels: ['agent-pr'], files: [HOST_FILE], comments: [comment()] })
194197
const r = main({ gh, env: { REVIEW_POLICY: POLICY, GH_REPO: 'ThinkFlowLab/FlyBot', PR: '566' }, repo: 'ThinkFlowLab/FlyBot', pr: 566 })
195198
assert.equal(r.ok, true, r.reasons.join('; '))
196199
assert.equal(r.command, `gh pr merge 566 --auto --squash --match-head-commit ${HEAD}`)
@@ -200,21 +203,21 @@ test('#718: a Closes reference and the agent-pr label let it through', () => {
200203
// `Fixes ThinkFlowLab/FlyBot#801`. The bare spelling there points at a number the fork does not have, so
201204
// the guard has to accept the qualified one or every fork pull request is refused at the last step.
202205
test('a cross-repository Fixes reference is an issue reference', () => {
203-
const gh = fakeGh({ body: 'Fixes ThinkFlowLab/FlyBot#801\n\nthe detail', labels: ['agent-pr'], files: [HOST_FILE], comments: [comment()] })
206+
const gh = fakeGh({ body: HEADERS + 'Fixes ThinkFlowLab/FlyBot#801\n\nthe detail', labels: ['agent-pr'], files: [HOST_FILE], comments: [comment()] })
204207
const r = main({ gh, env: { REVIEW_POLICY: POLICY, GH_REPO: 'ThinkFlowLab/FlyBot', PR: '566' }, repo: 'ThinkFlowLab/FlyBot', pr: 566 })
205208
assert.equal(r.ok, true, r.reasons.join('; '))
206209
})
207210

208211
test('the bare Fixes #<n> spelling still counts next to the cross-repository one', () => {
209-
const gh = fakeGh({ body: 'Fixes #801\n\nthe detail', labels: ['agent-pr'], files: [HOST_FILE], comments: [comment()] })
212+
const gh = fakeGh({ body: HEADERS + 'Fixes #801\n\nthe detail', labels: ['agent-pr'], files: [HOST_FILE], comments: [comment()] })
210213
const r = main({ gh, env: { REVIEW_POLICY: POLICY, GH_REPO: 'ThinkFlowLab/FlyBot', PR: '566' }, repo: 'ThinkFlowLab/FlyBot', pr: 566 })
211214
assert.equal(r.ok, true, r.reasons.join('; '))
212215
})
213216

214217
// The space after the keyword was optional before the cross-repository form existed and stays optional:
215218
// widening the pattern must not quietly drop a spelling that used to count.
216219
test('a Fixes reference with no space before the hash still counts', () => {
217-
const gh = fakeGh({ body: 'Fixes#801\n\nthe detail', labels: ['agent-pr'], files: [HOST_FILE], comments: [comment()] })
220+
const gh = fakeGh({ body: HEADERS + 'Fixes#801\n\nthe detail', labels: ['agent-pr'], files: [HOST_FILE], comments: [comment()] })
218221
const r = main({ gh, env: { REVIEW_POLICY: POLICY, GH_REPO: 'ThinkFlowLab/FlyBot', PR: '566' }, repo: 'ThinkFlowLab/FlyBot', pr: 566 })
219222
assert.equal(r.ok, true, r.reasons.join('; '))
220223
})
@@ -327,3 +330,94 @@ test('#925: a selfReview login with admin on someone else\'s pull request is acc
327330
assert.equal(r.verdict.by, 'Birfy')
328331
assert.ok(gh.calls.includes('repos/ThinkFlowLab/FlyBot/collaborators/Birfy/permission'))
329332
})
333+
334+
// #943, part 1: the two lines the pipeline has been described with everywhere, read before the queue.
335+
// #940 went from opened to merged in about seven minutes with the largest diff of the day and its body
336+
// carried neither line, so the convention was a sentence nothing checked. Both lines present is not
337+
// enough on its own — the Reviewer named has to be someone who may write a conclusion at all.
338+
test('#943: a body with no Owner: line is refused, and the refusal names that line', () => {
339+
const r = run({ body: 'Reviewer: reviewer-agent\nFixes #943\n', comments: [comment()] })
340+
assert.equal(r.ok, false)
341+
assert.ok(r.reasons.includes('missing line: Owner:'), r.reasons.join('; '))
342+
assert.equal(r.reasons.some((reason) => /missing line: Reviewer:/.test(reason)), false, 'only the line that is missing is named')
343+
})
344+
345+
test('#943: a body with no Reviewer: line is refused, and the refusal names that line', () => {
346+
const r = run({ body: 'Owner: Dev\nFixes #943\n', comments: [comment()] })
347+
assert.equal(r.ok, false)
348+
assert.ok(r.reasons.includes('missing line: Reviewer:'), r.reasons.join('; '))
349+
assert.equal(r.reasons.some((reason) => /missing line: Owner:/.test(reason)), false, 'only the line that is missing is named')
350+
})
351+
352+
test('#943: a Reviewer: naming a login outside `reviewers` is refused, and the refusal names the login', () => {
353+
const r = run({ body: 'Owner: Dev\nReviewer: someone-else\nFixes #943\n', comments: [comment()] })
354+
assert.equal(r.ok, false)
355+
assert.ok(r.reasons.includes('Reviewer names "someone-else", who is not in reviewers'), r.reasons.join('; '))
356+
})
357+
358+
test('#943: an @ and any case are fine on a login `reviewers` lists', () => {
359+
const r = run({ body: 'Owner: Dev\nReviewer: @Reviewer-Agent, BIRFY\nFixes #943\n', comments: [comment()] })
360+
assert.equal(r.ok, true, r.reasons.join('; '))
361+
})
362+
363+
// #941's shape: the body starts with the agent's own HTML comment, so the lines sit on the 4th and 5th
364+
// line, not on the first two. Reading only the first two would refuse every agent pull request.
365+
test('#943: the lines are found in the first 20 lines, not only the first two', () => {
366+
const r = run({ body: '<!-- CURSOR_AGENT_PR_BODY_BEGIN -->\n\n' + HEADERS + 'Fixes #943\n', comments: [comment()] })
367+
assert.equal(r.ok, true, r.reasons.join('; '))
368+
})
369+
370+
test('#943: lines after the first 20 do not count', () => {
371+
const far = 'Fixes #943\n' + 'filler\n'.repeat(19) + HEADERS
372+
const r = run({ body: far, comments: [comment()] })
373+
assert.equal(r.ok, false)
374+
assert.ok(r.reasons.includes('missing line: Owner:'), r.reasons.join('; '))
375+
assert.ok(r.reasons.includes('missing line: Reviewer:'), r.reasons.join('; '))
376+
})
377+
378+
// A draft is not entering the queue, and history is never re-read: the check runs on the enqueue request
379+
// for a live head only, so #940's merged record stays exactly as it is.
380+
test('#943: a draft is not refused by the body check', () => {
381+
const r = run({ body: 'Fixes #943\n', draft: true, comments: [comment()] })
382+
assert.equal(r.ok, true, r.reasons.join('; '))
383+
})
384+
385+
// #943, part 2: a GitHub bot account writes no conclusion. The shared account every Bot writes as
386+
// (`Birfy`) is a `User` and keeps counting, which is why this is a rule about account type and not a
387+
// login list — a list containing `Birfy` would void the whole pipeline.
388+
test('#943: a Verdict: written by a bot account is not a conclusion here either', () => {
389+
const bot = { user: { login: 'cursor[bot]', type: 'Bot' }, body: report(), created_at: '2026-10-24T07:40:42Z', author_association: 'NONE' }
390+
const shared = { user: { login: 'Birfy', type: 'User' }, body: report().replace('reviewer-agent', 'Birfy'), created_at: '2026-10-24T07:45:10Z', author_association: 'MEMBER' }
391+
392+
const alone = run({ comments: [bot] })
393+
assert.equal(alone.ok, false)
394+
assert.match(alone.reasons.join('; '), /cursor\[bot\].*bot account/)
395+
assert.equal(alone.verdict.judged.find((e) => e.login === 'cursor[bot]').counts, false)
396+
397+
const both = run({ comments: [bot, shared] })
398+
assert.equal(both.ok, true, both.reasons.join('; '))
399+
assert.equal(both.verdict.by, 'Birfy')
400+
})
401+
402+
test('#943: a Reviewer: line with no login names nobody, so it is the missing line', () => {
403+
const r = run({ body: 'Owner: Dev\nReviewer:\nFixes #943\n', comments: [comment()] })
404+
assert.equal(r.ok, false)
405+
assert.ok(r.reasons.includes('missing line: Reviewer:'), r.reasons.join('; '))
406+
})
407+
408+
// #943 INC at 87b49961: this check runs again on every comment (enqueue-guard.yml, `issue_comment`),
409+
// so "a merged pull request is never re-judged" was only true of the code that never read `merged`.
410+
// A sentence on #926/#925/#908 — merged without these lines, their bodies uneditable now — turned
411+
// their check red. A merged pull request is history; only a live request is judged.
412+
test('#943 INC: a merged pull request is not re-judged when a comment arrives', () => {
413+
const body = 'Fixes #602\n\nthe detail' // no Owner:/Reviewer:, the shape #926/#925/#908 merged with
414+
const merged = run({ body, merged: true, comments: [comment()] })
415+
assert.equal(merged.ok, true, merged.reasons.join('; '))
416+
assert.equal(merged.reasons.some((reason) => /missing line:/.test(reason)), false)
417+
// The negative control: the same body on an open, non-draft pull request is still refused, so the
418+
// exemption is `merged` and not a missing fixture field.
419+
const live = run({ body, comments: [comment()] })
420+
assert.equal(live.ok, false)
421+
assert.ok(live.reasons.includes('missing line: Owner:'), live.reasons.join('; '))
422+
assert.ok(live.reasons.includes('missing line: Reviewer:'), live.reasons.join('; '))
423+
})

‎.github/scripts/gate-check.test.mjs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const HOST_FILE = 'core/src/host/turns.js' // T2: needs a PASS
1313
const TEST_FILE = 'core/test/gate-check.test.js' // T1: needs none
1414
const REPO = 'Birfy/FlyBot'
1515

16+
// #943: a live, non-draft pull request carries the two lines that say who owns and reviews it.
17+
const HEADERS = 'Owner: Dev\nReviewer: reviewer-agent\n'
18+
1619
const policyFile = () => {
1720
const path = join(mkdtempSync(join(tmpdir(), 'gate-check-policy-')), 'review-policy.json')
1821
writeFileSync(path, JSON.stringify({ reviewers: ['reviewer-agent', 'Birfy'], selfReview: ['Birfy'], writers: ['reviewer-agent', 'Reviewer', 'Birfy'] }))
@@ -27,7 +30,7 @@ const comment = (body = report(), at = '2026-10-23T07:00:00Z') =>
2730

2831
// A repository with one pull request and the check runs written on it so far. `gh` answers both call
2932
// shapes the scripts use: variadic reads (`gh('api', …, path)`) and writes (`gh([args], json)`).
30-
const world = ({ head = HEAD, labels = ['agent-pr'], body = 'Fixes #812\n', comments = [], files = [HOST_FILE], author = 'opener', headRepo = REPO } = {}) => {
33+
const world = ({ head = HEAD, labels = ['agent-pr'], body = HEADERS + 'Fixes #812\n', comments = [], files = [HOST_FILE], author = 'opener', headRepo = REPO } = {}) => {
3134
const state = { head, labels, body, comments, files, author, headRepo, runs: [], writes: [], nextId: 100 }
3235
const pull = () => ({ head: { sha: state.head, repo: { full_name: state.headRepo } }, user: { login: state.author }, body: state.body, labels: state.labels.map((name) => ({ name })) })
3336
const handle = (args, input) => {
@@ -183,7 +186,7 @@ test('#812: a comment arriving after a refusal PATCHes the same run green', () =
183186
})
184187

185188
test('#812: a T0/T1 head is green without a PASS, and the summary says why', () => {
186-
const state = world({ body: 'Fixes #812\n', files: [TEST_FILE], comments: [] })
189+
const state = world({ body: HEADERS + 'Fixes #812\n', files: [TEST_FILE], comments: [] })
187190
const r = run(state)
188191
assert.equal(r.applied.conclusion, 'success')
189192
assert.equal(state.runs[0].conclusion, 'success')

0 commit comments

Comments
 (0)