From GitHub Actions to Authenticated URL: A Better Way to Share CI Artifacts
CI produces HTML worth reading by people who never open GitHub: coverage reports, Playwright runs, build previews, agent-authored dossiers. GitHub's own artifact storage locks those behind a login and a 90-day clock. Thi

CI produces HTML worth reading by people who never open GitHub: coverage reports, Playwright runs, build previews, agent-authored dossiers. GitHub's own artifact storage locks those behind a login and a 90-day clock. This guide swaps one workflow step so the report lands at a company-gated URL that renders in the browser and never expires.
The problem with GitHub Actions artifact storage
GitHub Actions stores build artifacts in a zip archive. To access them:
- Go to the Actions run in GitHub
- Find the artifact section
- Click Download
- Extract the zip
- Open the HTML file locally
This works for engineers. It's unusable for everyone else.
Your PM can't access private repository Actions without a GitHub account. Your QA manager shouldn't need to navigate GitHub's CI interface to read a test report. Your VP shouldn't be extracting zip files to see a build summary.
And after 90 days, the artifact is gone. GitHub retains Actions artifacts for 90 days by default on public repos (up to 400 configurable on private repos), per the GitHub Actions usage-limits docs. Any link shared in a Slack thread, Notion document, or Jira ticket goes dead when that window closes.
The one-line change
Before:
- uses: actions/upload-artifact@v4
with:
name: research-dossier
path: ./research-dossier/After:
- name: Publish dossier
run: |
npm install -g @displaydev/cli
dsp publish ./research-dossier/ --name "dossier-${{ github.run_id }}"
env:
DISPLAYDEV_API_KEY: ${{ secrets.DISPLAYDEV_API_KEY }}The URL never changes, requires a company email to access, and renders in the browser without downloading.
Full workflow example
name: Test and Publish
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Run tests
run: npm test -- --coverage
continue-on-error: true
- name: Build
run: npm run build
- name: Publish test coverage report
if: always()
run: |
npm install -g @displaydev/cli
# dsp publish prints the URL on stdout line 1 and a status line on line 2;
# head -n 1 captures just the URL.
COVERAGE_URL=$(dsp publish ./coverage/lcov-report/ \
--name "coverage-${{ github.sha }}" | head -n 1)
echo "COVERAGE_URL=$COVERAGE_URL" >> $GITHUB_ENV
env:
DISPLAYDEV_API_KEY: ${{ secrets.DISPLAYDEV_API_KEY }}
- name: Comment on PR
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `📊 **Coverage report:** ${process.env.COVERAGE_URL}`
})Use cases this covers
Agent-authored artifacts: Multi-page research dossiers, market maps, and competitive landscapes generated by a coding agent in CI. With more than half of professional developers now using AI coding tools daily (Stack Overflow 2025 Developer Survey), more of what CI generates is agent-written HTML meant for non-engineers to read.
Build outputs: Preview builds for non-engineers. PMs and designers can review what's shipping without needing a local development environment.
Scheduled report generation: Nightly builds, weekly analytics exports, automated competitive monitoring reports, anything a scheduled workflow generates and stakeholders need to read.
Viewer experience
The PR comment appears with a URL. The QA lead clicks it, signs in with Google in one click, and sees the full interactive test report in their browser. No GitHub account, no zip file, no 90-day expiry.
The URL persists in Slack threads, Notion documents, and Jira tickets indefinitely.
Cost comparison
| GitHub Actions artifact storage | display.dev | |
|---|---|---|
| Viewer needs GitHub account | ✅ | ❌ |
| Viewer needs private repo access | ✅ | ❌ |
| Artifact expiry | 90 days | Never |
| Browser-renderable (no download) | ❌ | ✅ |
| Company SSO | ❌ | ✅ |
| Monthly cost | $0 (included) | €49 flat |
FAQ
Can I publish only on failure?
Yes. Use if: failure() in the step condition. The artifact is only published when the workflow fails.
How long are display.dev artifacts retained?
Indefinitely, until you delete them. No expiry.
What about other CI systems (GitLab, CircleCI, Jenkins)?
The display CLI works anywhere Node.js runs. For GitLab CI: yaml publish: stage: report script: - npm install -g @displaydev/cli - dsp publish ./report/ --name "ci-$CI_PIPELINE_ID" variables: DISPLAYDEV_API_KEY: $DISPLAYDEV_API_KEY Same pattern for CircleCI and Jenkins: install the CLI, call dsp publish.
How do I keep one stable URL instead of a new link per run?
Publish to a fixed artifact ID instead of a per-run name. Store the shortId in a repo variable and update in place: yaml - run: | npm install -g @displaydev/cli BASE_VERSION=$(dsp get "${{ vars.DISPLAY_ARTIFACT_ID }}" | jq -r .currentVersion) dsp publish ./coverage/lcov-report/ \ --id "${{ vars.DISPLAY_ARTIFACT_ID }}" --base-version "$BASE_VERSION" env: DISPLAYDEV_API_KEY: ${{ secrets.DISPLAYDEV_API_KEY }} The URL stays the same; every run adds a version to history (50 versions on Pro). Use a per-run --name when you want a distinct URL per commit (as in the PR-comment example above), and a fixed --id for a single "latest report" link.
What token should CI use, and is it safe?
A scoped API key stored in DISPLAYDEV_API_KEY (a repo secret). Scoped keys are a Pro capability, so CI can publish without a human dsp login. The key only grants publish access; viewers still authenticate with their own company accounts.
Who can open the published report?
Anyone with an email on your company domain, after they sign in with Google or Microsoft (Pro) or a one-time passcode (Free). The QA lead, PM, or VP opens the link with no GitHub account and no repo access.
Free tier. No credit card. One-time password auth for viewers on free, Google + Microsoft SSO on Pro (€49/month flat).