From GitHub Actions to Authenticated URL: A Better Way to Share CI Artifacts
GitHub Actions stores build artifacts in a zip archive. To access them:

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. Any link shared in a Slack thread, Notion document, or Jira ticket goes dead.
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 is permanent, 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.
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.
Free tier. No credit card. One-time password auth for viewers on free, Google + Microsoft SSO on Pro (€49/month flat).