# GitHub Actions Artifact HTML Won't Open in the Browser — Why, and What to Do

Canonical: https://commareports.com/fix/github-actions-artifact-html-not-viewable
Published: 2026-09-07

> GitHub Actions serves every artifact as a zip download, so an HTML report inside one can never render in the browser. The reason, the four workarounds, and the one that produces a link.

# GitHub Actions won't show your HTML report

The workflow runs. `actions/upload-artifact` succeeds. The run page shows
`playwright-report` in the Artifacts box. You click it and get
`playwright-report.zip` in your Downloads folder.

That is the whole feature. There is no viewer.

## Why it is a zip, always

The artifacts endpoint responds with `Content-Type: application/zip` no
matter what the archive contains. A single `index.html` is still a zip
of one file. This is not an oversight and there is no flag that changes
it:

Artifacts are written by workflow code, and workflow code is
attacker-influenceable on any repository that accepts pull requests. If
github.com rendered that HTML at a github.com origin, every artifact
would be a stored-XSS payload aimed at logged-in users, with access to
their session for that origin. GitHub ships the same mitigation on
`raw.githubusercontent.com`, which returns `text/plain` for `.html` so
the browser refuses to execute it.

So the constraint is structural. Work with it rather than against it.

## What people try, and how each one goes

**Download and open locally.** Works, for you. Everyone else in the PR
thread has to repeat it, and half of them won't. Worse, many reports
break when opened from `file://` — see
[my HTML report lost its CSS](/html-report-broken-css).

**GitHub Pages.** Real hosting, real URL. But one Pages site per
repository means concurrent branches fight over the same tree unless you
invent `/<branch>/<run-id>/` paths and a cleanup job. Private
repositories need a paid plan for private Pages, and a `pull_request`
run from a fork has no write token, so the branch that most needs a
preview is the one that can't publish. Alternatives are covered in
[GitHub Pages alternatives](/alternatives/github-pages-alternatives).

**S3 or a bucket behind CloudFront.** Fine, and a lot of teams end here.
The cost is the setup: a bucket policy, an OIDC role for the workflow,
lifecycle rules, and a `Content-Type` mapping — get that last one wrong
and the browser downloads the file instead of rendering it
([why that happens](/fix/s3-html-downloads-instead-of-opening)).
Comments still live somewhere else.

**An artifact-proxy action.** Several exist. They re-serve the zip from
a third-party origin, which trades GitHub's XSS problem for that
origin's, and none of them survive a repository going private.

## The one-step version

Add a publish step. It does not need a bucket, a role, or a Pages
branch:

```yaml
- name: Publish report
  if: always() # a red build is when someone actually wants it
  run: |
    curl -sS -X PATCH \
      "https://commareports.com/api/v1/reports/${{ vars.COMMA_REPORT_ID }}" \
      -H "Authorization: Bearer ${{ secrets.COMMA_API_TOKEN }}" \
      -H "Content-Type: application/json" \
      --data @<(jq -Rs '{html: .}' playwright-report/index.html)
```

`if: always()` matters more than it looks. The default is to skip
publish steps when a prior step failed, which means the report gets
published on exactly the runs nobody needs it for.

Because the report id is fixed, every run appends a **revision** at the
same URL. The link in your PR template never goes stale, and any two
runs can be diffed against each other. Full setup, including creating
the report id once and the multi-file variant for reports that ship an
assets directory, is in [publish from CI](/docs/ci) and
[GitHub Actions HTML reports](/ci/github-actions-html-report).

## Why a link beats a download for review

The reason you uploaded the artifact was so someone would look at it.
Once it is a URL:

- It opens on a phone, in a Slack unfurl, from a PR comment.
- Reviewers select the failing assertion and comment **on that line**,
  instead of screenshotting it into chat —
  [comment on HTML](/comment-on-html).
- Access is per-report: private to you, to invited people, to anyone
  with the link, or public.
- Nothing expires at 90 days.

## Try it

Comma is free — unlimited reports, unlimited commenters, unlimited
revision history.

**[Publish your first CI report →](https://commareports.com/)**

### Related

- [GitHub Actions HTML reports](/ci/github-actions-html-report) · [Publish from CI](/docs/ci)
- [My CI artifact link expired](/fix/ci-artifact-expired)
- [GitHub Pages alternatives](/alternatives/github-pages-alternatives)
- [Share a Playwright report](/share-playwright-report)
