Update existing comment once user fixes issues

This commit is contained in:
Alicia Sykes 2026-02-27 23:12:10 +00:00
parent 3baabcafe5
commit 259314537a
3 changed files with 207 additions and 22 deletions

View file

@ -7,6 +7,7 @@ on:
permissions:
actions: read
contents: read
pull-requests: write
jobs:
@ -16,6 +17,8 @@ jobs:
if: github.event.workflow_run.event == 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Download PR metadata
id: download
continue-on-error: true
@ -26,20 +29,13 @@ jobs:
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Post comment
- name: Resolve PR context
id: context
uses: actions/github-script@v7
with:
github-token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const marker = '<!-- pr-check-bot -->';
// Check if there are findings to post
const commentFile = 'pr-meta/comment.md';
if (!fs.existsSync(commentFile)) {
console.log('No findings to post — skipping.');
return;
}
// Determine the PR number
let prNumber;
@ -48,8 +44,6 @@ jobs:
prNumber = parseInt(fs.readFileSync(numberFile, 'utf8').trim());
}
if (!prNumber) {
// workflow_run.pull_requests is empty for fork PRs, so
// fall back to searching by head SHA if needed
const prs = context.payload.workflow_run.pull_requests;
if (prs && prs.length > 0) {
prNumber = prs[0].number;
@ -72,23 +66,69 @@ jobs:
}
}
// Skip if we already commented on this PR
// Fetch existing bot comment (if any) and write to file for Python
const marker = '<!-- pr-check-bot -->';
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
});
if (comments.some(c => c.body.includes(marker))) {
console.log('Bot comment already exists — skipping.');
const existing = comments.find(c => c.body.includes(marker));
if (existing) {
fs.mkdirSync('pr-meta', { recursive: true });
fs.writeFileSync('pr-meta/existing-comment.md', existing.body);
fs.writeFileSync('pr-meta/existing-comment-id.txt', String(existing.id));
}
core.setOutput('pr_number', prNumber);
- name: Prepare comment
if: steps.context.outputs.pr_number
env:
CHECK_RUN_ID: ${{ github.event.workflow_run.id }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: python lib/checks/prepare-comment.py
- name: Post or update comment
if: steps.context.outputs.pr_number
uses: actions/github-script@v7
with:
github-token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const actionFile = 'pr-meta/action.txt';
if (!fs.existsSync(actionFile)) return;
const action = fs.readFileSync(actionFile, 'utf8').trim();
if (action === 'skip') {
console.log('Nothing to do — skipping.');
return;
}
// Post the comment
const body = fs.readFileSync(commentFile, 'utf8').trim();
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
const bodyFile = 'pr-meta/final-comment.md';
if (!fs.existsSync(bodyFile)) {
console.log('No comment body found — skipping.');
return;
}
const body = fs.readFileSync(bodyFile, 'utf8').trim();
const prNumber = parseInt('${{ steps.context.outputs.pr_number }}');
if (action === 'create') {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
console.log('Created bot comment.');
} else if (action === 'update') {
const commentId = parseInt(fs.readFileSync('pr-meta/existing-comment-id.txt', 'utf8').trim());
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: commentId,
body,
});
console.log('Updated bot comment.');
}

View file

@ -108,6 +108,8 @@ def main():
f.write(run_id)
findings = collect_findings()
with open(os.path.join(OUTPUT_DIR, "findings-count.txt"), "w") as f:
f.write(str(len(findings)))
changes_summary = load_diff_summary()
write_step_summary(findings)

View file

@ -0,0 +1,143 @@
"""Decides whether to create, update, or skip the PR bot comment.
Reads:
pr-meta/comment.md new comment from format-comment.py
pr-meta/findings-count.txt number of findings (from format-comment.py)
pr-meta/existing-comment.md current bot comment on the PR (from workflow)
Writes:
pr-meta/action.txt "create", "update", or "skip"
pr-meta/final-comment.md the body to post or update with
"""
import os
import re
WORK_DIR = "pr-meta"
def read_file(path):
"""Read a file and return its stripped content, or None if missing/empty."""
try:
with open(path) as f:
content = f.read().strip()
return content if content else None
except Exception:
return None
def read_findings_count(new_body):
"""Return the findings count from findings-count.txt, or by counting bullets."""
raw = read_file(os.path.join(WORK_DIR, "findings-count.txt"))
if raw is not None:
try:
return int(raw)
except ValueError:
pass
# Fallback: count bullet lines before <details> (diff summary has its own bullets)
body_before_details = new_body.split("<details>")[0]
return len(re.findall(r"^- .+$", body_before_details, re.MULTILINE))
def _was_already_passing(existing_body):
"""Check if the most recent state in the comment is already all-clear."""
# If there's a previous "all passing" edit, the last state was passing
if re.search(r"^Edit(?: \d+)?: All checks are (now passing|passing now)", existing_body, re.MULTILINE):
return True
# If there are no edits at all, check the original comment body
if not re.search(r"^Edit(?: \d+)?:", existing_body, re.MULTILINE):
return "All our automated checks have passed" in existing_body
return False
def _previous_failing_count(existing_body):
"""Extract the findings count from the most recent state in the comment."""
# Check edit lines first (most recent state)
matches = re.findall(r"^Edit(?: \d+)?: (\d+) checks? (?:is|are) still failing", existing_body, re.MULTILINE)
if matches:
return int(matches[-1])
# No edits — count bullets in the original comment (before <details>)
if not re.search(r"^Edit(?: \d+)?:", existing_body, re.MULTILINE):
body_before_details = existing_body.split("<details>")[0]
bullets = re.findall(r"^- .+$", body_before_details, re.MULTILINE)
return len(bullets) if bullets else None
return None
def build_edit_line(existing_body, findings_count, check_run_id, repo):
"""Build the edit line to append, or None if nothing to do."""
run_tag = f"<!-- run:{check_run_id} -->"
# Idempotency: this run was already processed
if run_tag in existing_body:
return None
# Skip if the state hasn't changed
if findings_count == 0 and _was_already_passing(existing_body):
return None
if findings_count > 0 and _previous_failing_count(existing_body) == findings_count:
return None
# Count previous edits to determine the next number
edits = re.findall(r"^Edit(?: \d+)?:", existing_body, re.MULTILINE)
edit_count = len(edits)
next_edit = edit_count + 1
run_url = f"https://github.com/{repo}/actions/runs/{check_run_id}"
if findings_count == 0:
if edit_count == 0:
return f"Edit: All checks are now passing \U0001f389 {run_tag}"
return f"Edit {next_edit}: All checks are passing now \u2705 {run_tag}"
verb = "check is" if findings_count == 1 else "checks are"
return (
f"Edit {next_edit}: {findings_count} {verb} still failing, "
f"see [here]({run_url}) for details {run_tag}"
)
def write_output(action, body=""):
"""Write action.txt and (optionally) final-comment.md."""
os.makedirs(WORK_DIR, exist_ok=True)
with open(os.path.join(WORK_DIR, "action.txt"), "w") as f:
f.write(action)
if body:
with open(os.path.join(WORK_DIR, "final-comment.md"), "w") as f:
f.write(body)
def main():
check_run_id = os.environ.get("CHECK_RUN_ID", "")
repo = os.environ.get("GITHUB_REPOSITORY", "")
new_body = read_file(os.path.join(WORK_DIR, "comment.md"))
if not new_body:
write_output("skip")
return
existing_body = read_file(os.path.join(WORK_DIR, "existing-comment.md"))
# No existing comment — create a new one
if not existing_body:
write_output("create", new_body)
return
# Existing comment — build an edit line to append
if not check_run_id:
write_output("skip")
return
findings_count = read_findings_count(new_body)
edit_line = build_edit_line(existing_body, findings_count, check_run_id, repo)
if not edit_line:
write_output("skip")
return
updated = existing_body.rstrip() + "\n\n" + edit_line
write_output("update", updated)
if __name__ == "__main__":
main()