Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 43 additions & 18 deletions .github/workflows/comment-cop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
cancel-in-progress: true
steps:
- name: Scan diff for added multi-line comments
id: scan
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
Expand Down Expand Up @@ -114,7 +115,7 @@ jobs:
pullRequest(number: $pr) {
reviewThreads(first: 100, after: $after) {
pageInfo { hasNextPage endCursor }
nodes { id isResolved comments(first: 1) { nodes { body } } }
nodes { id isResolved comments(first: 1) { nodes { body author { login } } } }
}
}
}
Expand All @@ -132,29 +133,23 @@ jobs:
const seenKeys = new Set();
const toResolve = [];
for (const t of threads) {
const body = t.comments.nodes[0]?.body || '';
const m = /<!-- comment-cop:([^>\s]+) -->/.exec(body);
const root = t.comments.nodes[0];
// The marker alone is forgeable (anyone can paste it into their own
// review comment), so only trust threads this workflow opened.
if (root?.author?.login !== 'github-actions') continue;
const m = /<!-- comment-cop:([^>\s]+) -->/.exec(root.body || '');
if (!m) continue;
const key = m[1];
seenKeys.add(key);
if (!t.isResolved && !presentKeys.has(key)) toResolve.push(t.id);
}

// Auto-resolve threads whose flagged block is gone from the current diff.
if (toResolve.length > 0) {
const mut = `
mutation($id: ID!) {
resolveReviewThread(input: { threadId: $id }) { thread { id } }
}`;
for (const id of toResolve) {
try {
await github.graphql(mut, { id });
} catch (e) {
core.warning(`resolveReviewThread failed for ${id}: ${e.message}`);
}
}
core.info(`Resolved ${toResolve.length} stale comment-cop thread(s).`);
}
// Threads whose flagged block is gone from the current diff are
// resolved by the next step, which authenticates with a PAT: the
// Actions GITHUB_TOKEN cannot call the resolveReviewThread mutation
// ("Resource not accessible by integration") even with
// pull-requests: write.
core.setOutput('stale-threads', toResolve.join(' '));

// Post new line comments for groups not already flagged.
const fresh = groups.filter(g => !seenKeys.has(keyFor(g)));
Expand Down Expand Up @@ -189,3 +184,33 @@ jobs:
}
}
core.info(`Posted ${posted} review comment(s).`);

- name: Resolve stale threads
if: steps.scan.outputs.stale-threads
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
STALE_THREADS: ${{ steps.scan.outputs.stale-threads }}
with:
github-token: ${{ secrets.ROBOBUN_TOKEN }}
script: |
const ids = process.env.STALE_THREADS.split(' ').filter(Boolean);
const mut = `
mutation($id: ID!) {
resolveReviewThread(input: { threadId: $id }) { thread { id } }
}`;
let resolved = 0;
for (const id of ids) {
try {
await github.graphql(mut, { id });
resolved++;
} catch (e) {
core.warning(`resolveReviewThread failed for ${id}: ${e.message}`);
}
}
core.info(`Resolved ${resolved} of ${ids.length} stale comment-cop thread(s).`);
if (ids.length > 0 && resolved === 0) {
// Fail the step (continue-on-error keeps the job green) so a dead
// token shows up as an error annotation instead of silent warnings.
core.setFailed(`All ${ids.length} resolveReviewThread mutations failed; check ROBOBUN_TOKEN.`);
}