diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b65d9a..6f480c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,14 @@ on: pull_request: types: [opened, synchronize, reopened] +concurrency: + # Supersede an in-flight run for the SAME PR. Push runs deliberately get a + # unique group (run_id): a shared group keeps only one pending run, so rapid + # pushes to main would evict an intermediate commit's queued run before it + # ever ran, leaving that commit unverified. + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + jobs: detect-changes: name: Detect Changed Paths diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index 24ebbca..55f3dbf 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -15,6 +15,10 @@ jobs: with: script: | const labelName = 'needs triage'; + // Returns true when the label was ABSENT at the moment this run + // looked - whether this run created it or lost the race to a + // concurrent one. That is exactly the condition under which GitHub + // already dropped it from the opening issue's template frontmatter. const ensureLabel = async (name, color, description) => { try { await github.rest.issues.getLabel({ @@ -22,6 +26,7 @@ jobs: repo: context.repo.repo, name, }); + return false; } catch { try { await github.rest.issues.createLabel({ @@ -35,7 +40,11 @@ jobs: if (createError.status !== 422) { throw createError; } + // 422 means a concurrent run created it first. The label was + // still missing when THIS run looked, so this run's issue had + // its template label dropped too and needs the same recovery. } + return true; } }; const applyLabelIfMissing = async (name) => { @@ -49,8 +58,19 @@ jobs: }); } }; - await ensureLabel(labelName, 'ededed', 'Not yet reviewed or categorized'); - await applyLabelIfMissing(labelName); + // Every issue template already sets `needs triage` in its frontmatter, + // so applying it here unconditionally was pure duplication. ensureLabel + // must stay: GitHub silently drops a template label that does not exist + // in the repo, so this call is what creates it. + // + // The one case that still needs an explicit apply is the run that + // creates the label: the template's request was already dropped, and + // creating the label now does not retroactively label the issue that + // triggered this run. Every later issue gets it from its template. + const createdTriageLabel = await ensureLabel(labelName, 'ededed', 'Not yet reviewed or categorized'); + if (createdTriageLabel) { + await applyLabelIfMissing(labelName); + } if (!context.payload.issue.assignee) { await github.rest.issues.addAssignees({ owner: context.repo.owner, diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml index 64a237d..b8a8362 100644 --- a/.github/workflows/pr-comment.yml +++ b/.github/workflows/pr-comment.yml @@ -22,6 +22,20 @@ on: workflows: ["Build"] types: [completed] +# Serialize report runs for the same PR branch. The sticky-comment update is a +# lookup-then-create: two Build runs completing close together would both find +# no marker and both post, producing exactly the duplicate comments the marker +# exists to prevent. GitHub offers no atomic comment upsert, so serializing per +# PR is what makes the pair safe. Keyed on the trusted workflow_run head fields +# because the PR number is not resolved until a step below. +# +# cancel-in-progress is false so an in-flight post is never killed midway. A +# queued run can still be superseded by a newer one, which is correct here: the +# newest report is the one worth posting. +concurrency: + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: false + permissions: contents: read actions: read # download artifacts + read job conclusions from the run @@ -185,13 +199,24 @@ jobs: # The valgrind job prints ">>> valgrind " per test binary and # "::error::Valgrind reported ..." on a failing one. Count those markers # (integers only) - no fork string reaches the rendered body. + # A log with no ">>> valgrind" markers means the job changed its output + # format: say so in the comment. Dropping the chart silently is how a + # broken report goes unnoticed. The note is a trusted literal - no + # fork-controlled text reaches the body through this path. emit_valgrind_pie() { local logf="$1" total fails clean total="$(grep -Ec '^>>> valgrind ' "$logf" || true)" fails="$(grep -Ec '^::error::Valgrind reported' "$logf" || true)" - { [[ "$total" =~ ^[0-9]+$ ]] && [[ "$fails" =~ ^[0-9]+$ ]] && [ "$total" -gt 0 ] && [ "$fails" -le "$total" ]; } || return 0 + if ! { [[ "$total" =~ ^[0-9]+$ ]] && [[ "$fails" =~ ^[0-9]+$ ]] && [ "$total" -gt 0 ] && [ "$fails" -le "$total" ]; }; then + printf '_Chart unavailable: no ">>> valgrind" markers found in the log._\n\n' + return 0 + fi clean=$(( total - fails )) printf '```mermaid\n' + # Mermaid's default pie palette is dark blue/purple, which reads as + # arbitrary. Force clean=green, leaking=red: pie1 colors the first + # slice below, pie2 the second. + printf '%s\n' "%%{init: {'themeVariables': {'pie1': '#2da44e', 'pie2': '#cf222e'}}}%%" printf 'pie showData\n' printf ' title Test binaries under Valgrind (%s total)\n' "$total" printf ' "Clean" : %s\n' "$clean" @@ -199,6 +224,10 @@ jobs: printf '```\n\n' } + # Identifies our own report comment so each push updates it in place + # instead of appending one per push. Invisible in rendered markdown. + MARKER="" + : > body.md wrote=0 for spec in "${specs[@]}"; do @@ -248,4 +277,17 @@ jobs: exit 0 fi printf '_Commit `%s` - [full run](%s)_\n' "$HEAD_SHA" "$RUN_URL" >> body.md - gh pr comment "$PR" --repo "$REPO" --body-file body.md + printf '\n%s\n' "$MARKER" >> body.md + + # Update our own previous comment instead of stacking one per push. The + # author filter is load-bearing: anyone can post a comment containing + # the marker, and only a github-actions[bot] comment is ours to edit. + existing="$(gh api --paginate --slurp "repos/$REPO/issues/$PR/comments?per_page=100" \ + | jq -r --arg m "$MARKER" \ + '[.[][] | select(.user.login == "github-actions[bot]" and (.body | contains($m)))] | .[0].id // empty')" + if [ -n "$existing" ]; then + gh api -X PATCH "repos/$REPO/issues/comments/$existing" -F body=@body.md --silent + echo "Updated report comment $existing on PR #$PR." + else + gh pr comment "$PR" --repo "$REPO" --body-file body.md + fi