bot-bench-comment.yml

  1name: Bot - Benchmark PR Comment
  2
  3on:
  4  workflow_run:
  5    workflows: ["Benchmarks"]
  6    types: [completed]
  7
  8permissions:
  9  contents: read
 10  pull-requests: write
 11  actions: read
 12
 13jobs:
 14  comment:
 15    if: github.event.workflow_run.event == 'pull_request'
 16    runs-on: ubuntu-latest
 17    steps:
 18      - name: Download benchmarks artifact
 19        uses: actions/github-script@v9
 20        with:
 21          script: |
 22            const run_id = context.payload.workflow_run.id;
 23            const { data: list } = await github.rest.actions.listWorkflowRunArtifacts({
 24              owner: context.repo.owner,
 25              repo: context.repo.repo,
 26              run_id,
 27            });
 28            const art = list.artifacts.find(a => a.name === 'benchmarks');
 29            if (!art) {
 30              core.setFailed('benchmarks artifact missing');
 31              return;
 32            }
 33            const dl = await github.rest.actions.downloadArtifact({
 34              owner: context.repo.owner,
 35              repo: context.repo.repo,
 36              artifact_id: art.id,
 37              archive_format: 'zip',
 38            });
 39            const fs = require('fs');
 40            fs.writeFileSync('benchmarks.zip', Buffer.from(dl.data));
 41
 42      - name: Unzip
 43        run: |
 44          mkdir -p bench
 45          unzip -o benchmarks.zip -d bench
 46          ls -la bench
 47
 48      - name: Post comment
 49        uses: actions/github-script@v9
 50        with:
 51          github-token: ${{ secrets.HOMEBREW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
 52          script: |
 53            const fs = require('fs');
 54            const read = (p, fallback = '') => {
 55              try { return fs.readFileSync(p, 'utf8'); } catch (_) { return fallback; }
 56            };
 57
 58            const prRaw = read('bench/pr-number.txt').trim();
 59            const pr_number = parseInt(prRaw, 10);
 60            if (!pr_number) {
 61              core.info('no PR number recorded; skipping');
 62              return;
 63            }
 64
 65            const report = read('bench/benchstat.txt', '(empty)');
 66            const verdict = read('bench/verdict.txt');
 67            const parse = key => {
 68              const m = verdict.match(new RegExp("^" + key + "=(.+)$", "m"));
 69              return m ? m[1].trim() : '';
 70            };
 71            const status = parse('status') || 'neutral';
 72            const worse = parse('worse') || '0';
 73            const better = parse('better') || '0';
 74
 75            const headers = {
 76              regression: "### Benchmark report — regression detected",
 77              improvement: "### Benchmark report — improvement detected",
 78              neutral: "### Benchmark report — no significant change",
 79            };
 80            const body = [
 81              headers[status] || headers.neutral,
 82              `Metrics worse: **${worse}** · better: **${better}** (threshold: ±3%).`,
 83              "",
 84              "<details><summary>benchstat output</summary>",
 85              "",
 86              "```",
 87              report.trim() || "(empty)",
 88              "```",
 89              "",
 90              "</details>",
 91              "",
 92              "<sub>auto-generated by benchmarks.yml</sub>",
 93            ].join("\n");
 94
 95            const marker = "<sub>auto-generated by benchmarks.yml</sub>";
 96            const { data: comments } = await github.rest.issues.listComments({
 97              owner: context.repo.owner,
 98              repo: context.repo.repo,
 99              issue_number: pr_number,
100            });
101            const existing = comments.find(c => c.body && c.body.includes(marker));
102            if (existing) {
103              await github.rest.issues.updateComment({
104                owner: context.repo.owner,
105                repo: context.repo.repo,
106                comment_id: existing.id,
107                body,
108              });
109            } else {
110              await github.rest.issues.createComment({
111                owner: context.repo.owner,
112                repo: context.repo.repo,
113                issue_number: pr_number,
114                body,
115              });
116            }