Documents the three-incident history, the enforcement layers (inline comments + grep guard + ADR), how to spot the symptom, and the explicit upgrade trigger (act_runner v4 protocol support OR v3 CVE). Cross-references ADR-011 (single-tenant Gitea runner) and #557. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.8 KiB
ADR 014 — Pin actions/upload-artifact to v3 (Gitea act_runner v4 protocol incompatibility)
Status: Accepted
Date: 2026-05-14
Issues: #557 — re-regression · #14 — original incident
Context
actions/upload-artifact is available in two incompatible major versions. The v4 client
uploads via a GitHub-specific artifact API that is not implemented in Gitea's
act_runner (the self-hosted CI substrate established by ADR-011). When a workflow step
uses actions/upload-artifact@v4 on this runner, act_runner returns a non-zero exit
code from the v4 client even when all tests pass, producing:
green test suite — red job status — no artifact uploaded
The failure lands in the upload step, after the test output, making it hard to diagnose from the build log.
Incident history
| Date | Commit | Event |
|---|---|---|
| 2026-03-19 | 9f3f022e |
Original downgrade: upload-artifact@v4 → v3 |
| 2026-03-19 | 4142c7cd |
Rationale committed; closes #14 |
| 2026-05-05 | 410b91e2 |
Re-regression: upgraded back to v4 without referencing #14 |
| 2026-05-14 | this PR | Second downgrade + ADR + grep guard |
The root cause of the re-regression was institutional-memory failure: the original
rationale was captured only in a commit body, invisible at the point of change (the
uses: line). This ADR, the inline comments, and the grep guard are the three
defence layers that replace that missing breadcrumb.
Decision
Pin all actions/upload-artifact and actions/download-artifact call sites to @v3.
Both action families share the same v4 protocol incompatibility with act_runner.
Pinning to the major tag (@v3) keeps us on the latest v3 patch without Renovate noise.
Three call sites are pinned:
.gitea/workflows/ci.yml— "Upload coverage reports" step.gitea/workflows/ci.yml— "Upload screenshots" step.gitea/workflows/coverage-flake-probe.yml— "Upload coverage log on failure" step
Each pinned uses: line carries a load-bearing inline comment:
# Gitea Actions (act_runner) does not implement upload-artifact v4 protocol — pinned per ADR-014. Do NOT upgrade. See #557.
- uses: actions/upload-artifact@v3
A CI grep guard enforces the constraint automatically (see below).
Consequences
Enforcement layers (defence in depth)
- Inline comments on every
uses:line — visible at the point of change. - CI grep guard in
.gitea/workflows/ci.yml("Assert no (upload|download)-artifact past v3") — fails the build if a future commit re-introduces@v4or higher on any workflow file. Anchored to YAMLuses:lines to avoid false positives on embedded shell strings. Includes a self-test that proves the regex catches v4+ before scanning the repo. - This ADR — canonical rationale; cross-referenced by comments and guard message.
How to spot the symptom
- Test suite output shows green (vitest, surefire, pytest all exit 0)
- CI job status shows red
- Artifacts section of the run is empty
- Build log shows a non-zero exit from the
Upload …step immediately after green tests
@v3 maintenance-mode status
GitHub placed actions/upload-artifact@v3 in maintenance mode (no new features) but it
has not been removed and carries no known unpatched CVE as of this writing. If GitHub
publishes a v3-specific security advisory, that is an additional trigger to re-evaluate
(see upgrade conditions below).
When to remove this pin
Re-evaluate pinning when either condition is met:
gitea/act_runnerships a release with v4 artifact protocol support. Track upstream: https://gitea.com/gitea/act_runneractions/upload-artifact@v3acquires an unpatched CVE that cannot be mitigated at the runner level.
When upgrading: remove the grep guard step, update all three uses: lines, remove the
inline comments, and update this ADR's status to Superseded.
Alternatives
SHA pinning (uses: actions/upload-artifact@<sha>)
More secure against action repository compromise, but adds Renovate update friction and is disproportionate for a self-hosted, single-tenant Gitea instance with one trusted contributor (ADR-011). Rejected.
Minor/patch pinning (@v3.4.0)
Avoids Renovate PRs but freezes us on a specific patch. The v3 major track is in maintenance mode — minor pinning has no benefit and would require manual updates for any v3 security patches. Rejected.
Renovate packageRules bypass
Would prevent automated PRs from proposing v4. Not needed while Renovate is not configured for this repository. Revisit if Renovate is introduced.
Migrating the runner to a v4-compatible Gitea release
Out of scope for this issue. A separate decision; tracked in #557's non-goals.