fix(ci): artifacts move to stock upload-artifact@v7 / download-artifact@v8
test-go / test (push) Successful in 1m4s
test-go / integration (push) Successful in 3m54s
android / Build + lint + test (push) Successful in 4m58s
release / Build signed APK (releases and dev) (push) Successful in 5m11s
release / Build + push container image (push) Successful in 1m52s
release / Verify release artifacts (tag releases only) (push) Skipped

android.yml's debug upload and release.yml's minstrel-apk pair went
through the bvandeusen fork mirrors, with comments saying stock actions
refuse this hostname, that the pair had to be matched on the bundled
@actions/artifact major, and that download v7 was off-limits for node24.
None of that holds on gitea/runner 3.x: the runner edits the GHES refusal
out of the action bundles, every download major v4-v8 reads every upload
major v4-v7 (Scribe spike #3843, CI-runner run 6312), and every CI image
carries Node 24. The mirror pair itself was last verified at tag run 6286.

Same artifact names, paths and if-no-files-found. ci-requirements.md
drops the pairing table and keeps what is still true: @v3 is invisible.

Scribe snippet #2271, milestone 395.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DwoKYuw3qJmUUYsJeNherB
This commit is contained in:
2026-09-10 17:25:10 -04:00
co-authored by Claude Opus 5
parent aeb8781c4e
commit 8f4b76a638
3 changed files with 37 additions and 77 deletions
+6 -9
View File
@@ -80,15 +80,12 @@ jobs:
- name: Upload debug APK - name: Upload debug APK
if: github.event_name == 'push' && github.ref == 'refs/heads/main' if: github.event_name == 'push' && github.ref == 'refs/heads/main'
# Mirrored action, never actions/upload-artifact. @v4+ throws # Stock action: it works on this forge since the runner moved to
# GHESNotSupportedError client-side on the hostname (no server setting # gitea/runner 3.x, which edits upload-artifact's client-side GHES refusal
# reaches that check), and @v3 is worse — it reports success while Gitea # out of the action bundle (Scribe snippet #2271). Never @v3 — it reports
# serves artifacts back only through the v4 API, so the upload is stored # success while Gitea serves artifacts back only through the v4 API, and
# and invisible to every retrieval path. @v3 is what left 72 unreachable # it is what left 72 unreachable artifacts on this repo (Scribe 2270).
# artifacts on this repo. Pinned by SHA because the mirror auto-syncs; uses: actions/upload-artifact@v7
# full URL because DEFAULT_ACTIONS_URL sends bare owner/repo to github.com.
# See Scribe issues 2255 / 2270.
uses: https://git.fabledsword.com/bvandeusen/upload-artifact@cb8afe72b42edc798abfb8fcb556cf660d894245
with: with:
name: minstrel-android-debug-${{ github.sha }} name: minstrel-android-debug-${{ github.sha }}
path: android/app/build/outputs/apk/debug/app-debug.apk path: android/app/build/outputs/apk/debug/app-debug.apk
+11 -21
View File
@@ -232,13 +232,12 @@ jobs:
-PMINSTREL_VERSION_CODE=${{ steps.ver.outputs.code }} -PMINSTREL_VERSION_CODE=${{ steps.ver.outputs.code }}
- name: Upload APK as workflow artifact - name: Upload APK as workflow artifact
# Mirrored action, never actions/upload-artifact — @v4+ refuses on the # Stock action (snippet #2271) — never @v3, which uploads something Gitea
# hostname, @v3 uploads something Gitea will never serve back. This is # will never serve back. This is the producing half of a pair:
# the producing half of a pair: image-release downloads `minstrel-apk` # image-release downloads `minstrel-apk` below. Any upload v4+ pairs with
# below with the matching download-artifact mirror. Both must stay on # any download v4+ on this forge (every combination tested 2026-09-10,
# the v4 protocol — mixing a v3 upload with a v4 download (or the # Scribe spike #3843), so the two pins need not move together.
# reverse) yields an empty listing, not an error. See Scribe 2255 / 2270. uses: actions/upload-artifact@v7
uses: https://git.fabledsword.com/bvandeusen/upload-artifact@cb8afe72b42edc798abfb8fcb556cf660d894245
with: with:
name: minstrel-apk name: minstrel-apk
path: android/app/build/outputs/apk/release/app-release.apk path: android/app/build/outputs/apk/release/app-release.apk
@@ -435,20 +434,11 @@ jobs:
if: >- if: >-
steps.guard.outputs.ready == 'true' && steps.guard.outputs.ready == 'true' &&
(startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/dev') (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/dev')
# Consuming half of the pair — never actions/download-artifact. Same fork, # Consuming half of the pair: stock download-artifact, which works here for
# same reason: upstream's client-side GHES check rejects this hostname # the same reason as the upload (gitea/runner 3.x edits the GHES refusal
# before it connects. bvandeusen/download-artifact mirrors # out of the bundle; snippet #2271). v8 runs on node24, which every
# code.forgejo.org/forgejo/download-artifact. # CI-runner image carries — the runner uses the image's own node.
# uses: actions/download-artifact@v8
# SHA below is that fork's `v6` tag. Match on @actions/artifact, NOT on
# the action's own version number — the two actions release on unrelated
# cadences, and download v5 would pair a ^2.3.2 client with this file's
# ^4.0.0 uploader. v6 is the tag whose bundled library major (^4.0.0) is
# the same one proven against this instance by the upload side.
# Deliberately NOT v7: it moves to node24 and upstream requires runner
# >= 2.327.1 for it, which act_runner does not claim to satisfy.
# Pinned, not tagged — the mirror auto-syncs every 8h.
uses: https://git.fabledsword.com/bvandeusen/download-artifact@8d4e9521a5f7e5f8b6351f341f719f9f45a92a3a
with: with:
name: minstrel-apk name: minstrel-apk
path: client/ path: client/
+20 -47
View File
@@ -62,57 +62,30 @@ None.
- **Go toolchain pin.** `go.mod` is on `go 1.25.0` because `golang.org/x/crypto v0.51.0` declares 1.25 as its minimum. `ci-go:1.26` satisfies this with headroom. Future `x/crypto` bumps that move the Go floor should be paired with an image-tag bump in this file + the workflows. - **Go toolchain pin.** `go.mod` is on `go 1.25.0` because `golang.org/x/crypto v0.51.0` declares 1.25 as its minimum. `ci-go:1.26` satisfies this with headroom. Future `x/crypto` bumps that move the Go floor should be paired with an image-tag bump in this file + the workflows.
- **In-app update channel — `needs:`, not polling.** `release.yml`'s `image-release` job declares `needs: [android-release]`, so on tag pushes the signed APK is guaranteed present before the image build starts — no polling window, no race. (The old cross-workflow polling against `flutter.yml` is gone with that workflow.) On non-tag `main` pushes `android-release` is skipped and `image-release` instead pulls the most recent release's APK and reconstructs its exact `versionName`, so `:latest` never ships without an update channel. It degrades to an empty `client/` — never a wrong version — if no release, asset, or tag commit-count can be resolved. - **In-app update channel — `needs:`, not polling.** `release.yml`'s `image-release` job declares `needs: [android-release]`, so on tag pushes the signed APK is guaranteed present before the image build starts — no polling window, no race. (The old cross-workflow polling against `flutter.yml` is gone with that workflow.) On non-tag `main` pushes `android-release` is skipped and `image-release` instead pulls the most recent release's APK and reconstructs its exact `versionName`, so `:latest` never ships without an update channel. It degrades to an empty `client/` — never a wrong version — if no release, asset, or tag commit-count can be resolved.
- **Cache server reachability.** `test-web.yml` does NOT use `cache: 'npm'` on `actions/setup-node` — the Gitea Actions cache server isn't reachable from this runner's container network and `setup-node` was burning ~4m41s on ETIMEDOUT before failing open. With the migration to `ci-go:1.26`, `setup-node` is removed entirely (Node is in the image). The cache concern reappears if a future change re-introduces a network-dependent action. - **Cache server reachability.** `test-web.yml` does NOT use `cache: 'npm'` on `actions/setup-node` — the Gitea Actions cache server isn't reachable from this runner's container network and `setup-node` was burning ~4m41s on ETIMEDOUT before failing open. With the migration to `ci-go:1.26`, `setup-node` is removed entirely (Node is in the image). The cache concern reappears if a future change re-introduces a network-dependent action.
- **Artifacts — use the mirrored actions, never `actions/{upload,download}-artifact`.** - **Artifacts — stock `actions/upload-artifact@v7` and `actions/download-artifact@v8`; never `@v3`.**
```yaml ```yaml
uses: https://git.fabledsword.com/bvandeusen/upload-artifact@cb8afe72b42edc798abfb8fcb556cf660d894245 uses: actions/upload-artifact@v7
uses: https://git.fabledsword.com/bvandeusen/download-artifact@8d4e9521a5f7e5f8b6351f341f719f9f45a92a3a uses: actions/download-artifact@v8
``` ```
Upstream's `@v4+` cannot work against this instance and no server-side change Stock works on this forge since the runner moved to gitea/runner 3.x, which
will help: `isGhes()` rejects any hostname that isn't `github.com` / edits the actions' client-side `isGhes()` refusal out of their bundles. Proven
`*.ghe.com` / `*.localhost` and throws before it opens a connection, so the on 2026-09-10 for upload v4v7 and download v4v8 (Scribe spike #3843). Until
server is never asked what it supports. `@v3` is worse — it reports success, then this repo pinned SHA mirrors of the Forgejo project's forks, because
and Gitea then serves artifacts back only through the v4 API upstream threw on the hostname before it opened a connection (Scribe 2255).
(`content_encoding = application/zip`), so a v3 upload is stored but invisible
to every retrieval path. A green job producing nothing retrievable; that is how
72 unreachable artifacts accumulated on this repo. Scribe issues 2255 / 2270.
Both are pull mirrors of the Forgejo project's forks `@v3` is still broken: it reports success, and Gitea serves artifacts back only
(`code.forgejo.org/forgejo/{upload,download}-artifact`, one commit on upstream through the v4 API (`content_encoding = application/zip`), so a v3 upload is
disabling that check), mirrored so CI depends on commits we hold and pinned by stored but invisible to every retrieval path. That is how 72 unreachable
SHA because the mirrors auto-sync every 8h — a moved upstream tag would artifacts accumulated on this repo (Scribe 2270).
otherwise silently change what runs.
**Match the pins on `@actions/artifact`, not on the actions' own version **Pairing no longer needs managing.** This entry used to pin upload v5 against
numbers.** The two actions release on unrelated cadences, so equal version download v6 so both bundled `@actions/artifact` ^4.0.0, warning that a mismatch
numbers do NOT mean a compatible pair — upload `v5` bundles `@actions/artifact` across `release.yml`'s producer/consumer pair would list empty. Tested, and not
^4.0.0 while download `v5` bundles ^2.3.2. The pins above are upload **v5** and true on this instance: every download major v4v8 read the artifacts of every
download **v6**, which is the pairing that puts ^4.0.0 on both sides. This upload major v4v7, by name and by pattern (CI-runner run 6312). The only real
matters because `release.yml` is a producer/consumer pair — `android-release` protocol break is v3 → v4. node24 is no longer a concern either — every
uploads `minstrel-apk`, `image-release` downloads it — and a protocol mismatch CI-runner image carries Node 24 and the runner runs actions with the image's
across it yields an empty listing rather than an error, exactly the silent `node`.
failure this entry exists to prevent.
| tag | `@actions/artifact` | runtime |
|---|---|---|
| upload v4 | ^2.1.1 | node20 |
| **upload v5** ← pinned | **^4.0.0** | node20 |
| download v4 | ^2.1.1 | node20 |
| download v5 | ^2.3.2 | node20 |
| **download v6** ← pinned | **^4.0.0** | node20 |
| download v7 | ^5.0.0 | **node24** |
The only true protocol break in this history was **v3 → v4** (upstream:
"Downloading artifacts that were created from `actions/upload-artifact@v3` and
below are not supported"); v4-and-up are one family. Later majors are mostly
ergonomics and runtime — upload v4 forbids re-uploading a name and caps a job
at 500 artifacts; download v5 made by-ID extraction match by-name.
**Do not jump the download pin to v7.** That major is a runner requirement, not
a feature change: it moves to `runs.using: node24` and upstream states it
"requires a minimum Actions Runner version of 2.327.1 … if you are using
self-hosted runners, ensure they are updated before upgrading." act_runner is
not GitHub's runner and makes no such version claim, so node24 is unverified
here. Everything currently pinned is node20.
Upload steps set `if-no-files-found: error` rather than the default `warn`, so Upload steps set `if-no-files-found: error` rather than the default `warn`, so
an upload that matches nothing fails its own job instead of failing the an upload that matches nothing fails its own job instead of failing the