Publish an HTML report from GitHub Actions
GitHub Actions will run anything that produces HTML — coverage, Lighthouse, Playwright, benchmarks, eval scoreboards, a generated data digest — and then give you exactly two ways to look at it, neither of which is looking at it.
actions/upload-artifact stores a zip. A reviewer opens the run page,
scrolls to Artifacts, downloads, unzips, and opens index.html from
~/Downloads — where the relative asset paths sometimes work and sometimes
don't. $GITHUB_STEP_SUMMARY renders markdown inline, which is genuinely
useful, but it strips scripts and iframes, ignores most styling, and caps out
at 1 MiB. Neither one produces a link you can paste into Slack that opens a
report.
So teams screenshot the artifact back into the PR thread, and the actual report — the thing with all the detail in it — never gets read.
The one-step fix
Store a scoped token (reports:write is enough) as a
repository secret, then add one step:
- name: Publish report to Comma
if: always()
env:
COMMA_API_TOKEN: ${{ secrets.COMMA_API_TOKEN }}
REPORT_ID: ${{ vars.COMMA_COVERAGE_REPORT_ID }}
run: |
curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
-H "Authorization: Bearer $COMMA_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile html coverage/index.html \
--arg title "Coverage — ${GITHUB_SHA:0:7}" \
'{title: $title, html: $html}')"
Three details in there earn their keep:
PATCH, notPOST. Create the report once by hand, put its id in a repository variable, and every run appends a revision at the same URL. One bookmark per suite, forever — and any two runs can be diffed.if: always(). The runs worth sharing are the red ones. Without this, the publish step is skipped exactly when the report matters.- A commit-stamped title. Revisions titled
Coverage — a1b2c3dmake the history read like a log instead of a pile of timestamps.
Posting the link back to the PR
The report URL is stable, so a PR comment is one more step — no marketplace action required:
- name: Comment the report link
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr comment "${{ github.event.number }}" \
--body "📊 [Coverage report](https://commareports.com/p/$REPORT_ID) — updated for ${GITHUB_SHA:0:7}"
Reviewers click once and land on the rendered report with a comment layer on it, instead of a zip.
What you get that an artifact can't give you
- Comments anchored to the report. A reviewer highlights the row for the module that lost 8% coverage and pins a thread to it. The thread survives the next twelve runs — see commenting on HTML.
- Revision history with diffs. "What changed since the last green build?" becomes a diff instead of a memory exercise. Artifacts have no concept of a previous version.
- Access without repo access. Artifacts require a GitHub seat on the repository. A Comma report can be team-visible, domain-gated, or shared with a named reviewer who will never have a GitHub login — see the sharing model.
- No expiry. Revisions stay. The 90-day artifact clock doesn't apply.
- Announcements for free. A webhook on
revision.createdposts each new run to Slack or Discord, so the Slack message goes back to being a notification and the report stays the artifact.
Limits worth knowing before you wire it up
- HTML body: 5 MB. Screenshots, trace zips and JS bundles go in as assets instead — 25 MB per file, 250 MB per report.
- Scripts are stripped on write. Comma sanitizes incoming HTML, so a report that is a JavaScript application (Playwright's default reporter, Allure) should be published as a static digest with the full archive attached as an asset. Details in sharing a Playwright report.
- Rate limit: 60 requests/minute per token. One publish per build is not close.
- Keep the token scoped.
reports:writeonly. It can't read your other reports, and it's revocable from API tokens without touching CI.
When GitHub's own tools are the right answer
Be honest about it: if the artifact is a build output nobody reads, leave it
in upload-artifact. If your report is genuinely a multi-page site with
routing, GitHub Pages is the correct tool. Comma's unit is the report — one
document, rendered faithfully, with a review layer on top.
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited revision history. Take the workflow that currently uploads a zip, add the step above, and send the link instead.
Related
- Publish from CI — the general pipeline pattern
- GitLab CI HTML reports
- Jenkins HTML reports
- GitHub Pages alternatives