Fixes the artifact defect from Scribe issue 2270 on this repo. CI-only — three files, no application code.
What was wrong
android.yml and release.yml uploaded via actions/upload-artifact@v3, which reports success while Gitea stores the result in a format its v4-only artifact API will never serve back. 72 artifacts on this repo are on disk with valid DB rows and are invisible to the REST API, the web download route and the MCP tools alike. Green jobs producing nothing retrievable — the dangerous failure mode, not a red build.
actions/upload-artifact@v4+ is not the fix: its isGhes() rejects any hostname that isn't github.com / *.ghe.com / *.localhost and throws before opening a connection, so no server-side change reaches it.
What changed
file
site
change
android.yml
upload debug APK
→ upload-artifact mirror @ cb8afe72 (v5)
release.yml
upload minstrel-apk
→ upload-artifact mirror @ cb8afe72 (v5)
release.yml
download minstrel-apk
→ download-artifact mirror @ 8d4e9521 (v6)
Both point at pull mirrors of the Forgejo project's forks, which disable the client-side GHES refusal. Pinned by SHA because the mirrors auto-sync every 8h.
This repo consumes artifacts as well as producing them — image-release downloads minstrel-apk to bundle the APK into the container. Swapping only the upload would have left a v3 downloader reading the v1/v3 listing against a v4-protocol artifact: an empty result, not an error. So the download side needed a mirror too (bvandeusen/download-artifact, created for this).
Pin selection matches on @actions/artifact, not on the actions' version numbers — the two actions release on unrelated cadences, so upload v5 (^4.0.0) pairs with download v6 (^4.0.0), not download v5 (^2.3.2). ^4.0.0 is the library major just proven end to end on thoughtsync. Deliberately not download v7: that major moves to node24 and requires Actions Runner >= 2.327.1, which act_runner makes no claim to satisfy.
Uploads also set if-no-files-found: error so an upload that matches nothing fails its own job instead of failing the consumer later.
ci-requirements.md carries the full reasoning plus a version/runtime table.
Verification
The same swap is proven end to end on thoughtsync (run 3094): both artifacts listed via GET …/actions/runs/{id}/artifacts, downloaded from …/artifacts/{id}/zip, extracted, payloads genuine.
Merging here is what exercises it on this repo — android.yml's upload step is gated to main pushes, so dev cannot prove it. I'll verify the APK artifact lists and downloads once the main run completes.
Known gap:release.yml's upload+download pair is gated to v* tags, so it ships correct-by-analysis but unexercised until the next release.
Fixes the artifact defect from Scribe issue 2270 on this repo. CI-only — three files, no application code.
## What was wrong
`android.yml` and `release.yml` uploaded via `actions/upload-artifact@v3`, which reports success while Gitea stores the result in a format its v4-only artifact API will never serve back. **72 artifacts on this repo** are on disk with valid DB rows and are invisible to the REST API, the web download route and the MCP tools alike. Green jobs producing nothing retrievable — the dangerous failure mode, not a red build.
`actions/upload-artifact@v4+` is not the fix: its `isGhes()` rejects any hostname that isn't `github.com` / `*.ghe.com` / `*.localhost` and throws before opening a connection, so no server-side change reaches it.
## What changed
| file | site | change |
|---|---|---|
| `android.yml` | upload debug APK | → `upload-artifact` mirror @ `cb8afe72` (v5) |
| `release.yml` | upload `minstrel-apk` | → `upload-artifact` mirror @ `cb8afe72` (v5) |
| `release.yml` | download `minstrel-apk` | → `download-artifact` mirror @ `8d4e9521` (v6) |
Both point at pull mirrors of the Forgejo project's forks, which disable the client-side GHES refusal. Pinned by SHA because the mirrors auto-sync every 8h.
**This repo consumes artifacts as well as producing them** — `image-release` downloads `minstrel-apk` to bundle the APK into the container. Swapping only the upload would have left a v3 downloader reading the v1/v3 listing against a v4-protocol artifact: an empty result, not an error. So the download side needed a mirror too (`bvandeusen/download-artifact`, created for this).
**Pin selection matches on `@actions/artifact`, not on the actions' version numbers** — the two actions release on unrelated cadences, so upload v5 (`^4.0.0`) pairs with download **v6** (`^4.0.0`), not download v5 (`^2.3.2`). `^4.0.0` is the library major just proven end to end on thoughtsync. Deliberately not download v7: that major moves to `node24` and requires Actions Runner >= 2.327.1, which act_runner makes no claim to satisfy.
Uploads also set `if-no-files-found: error` so an upload that matches nothing fails its own job instead of failing the consumer later.
`ci-requirements.md` carries the full reasoning plus a version/runtime table.
## Verification
The same swap is proven end to end on thoughtsync (run 3094): both artifacts listed via `GET …/actions/runs/{id}/artifacts`, downloaded from `…/artifacts/{id}/zip`, extracted, payloads genuine.
Merging here is what exercises it on this repo — `android.yml`'s upload step is gated to `main` pushes, so `dev` cannot prove it. I'll verify the APK artifact lists and downloads once the `main` run completes.
**Known gap:** `release.yml`'s upload+download pair is gated to `v*` tags, so it ships correct-by-analysis but unexercised until the next release.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
android.yml and release.yml uploaded via actions/upload-artifact@v3, which
reports success while Gitea stores the result in a format its v4-only
artifact API will never serve back — 72 artifacts on this repo are on disk,
have valid DB rows, and are invisible to every retrieval path. Green jobs
producing nothing retrievable.
release.yml is a producer/consumer pair: android-release uploads
minstrel-apk and image-release downloads it to bundle into the container.
Swapping only the upload would have left download-artifact@v3 reading the
v1/v3 listing and finding nothing, so mirror the download side too —
bvandeusen/download-artifact, pull mirror of forgejo/download-artifact,
pinned at its v5 tag to match the upload pin's major.
Not actions/{upload,download}-artifact@v4: isGhes() throws on the hostname
before opening a connection, so no server-side change reaches it.
Upload steps also set if-no-files-found: error — image-release hard-depends
on minstrel-apk existing, so an empty upload must fail where it happens.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous pin matched the two actions by their own version numbers, which
is meaningless: upload-artifact and download-artifact release on unrelated
cadences. upload v5 bundles @actions/artifact ^4.0.0; download v5 bundles
^2.3.2. "v5 and v5" was in fact a mismatched pair.
download v6 is the tag that puts ^4.0.0 on both sides — and ^4.0.0 is the
library major just proven against this instance by the upload side
(thoughtsync run 3094: two artifacts listed, downloaded and extracted
intact). ^2.3.2 has never been exercised here.
Not v7: that major is a runner requirement rather than a feature change. It
moves to runs.using: node24 and upstream requires runner >= 2.327.1 for it,
which act_runner does not claim to satisfy. Everything pinned stays node20.
ci-requirements.md now carries the version/runtime table and the reasoning,
so the next person matches on the library instead of the tag number.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes the artifact defect from Scribe issue 2270 on this repo. CI-only — three files, no application code.
What was wrong
android.ymlandrelease.ymluploaded viaactions/upload-artifact@v3, which reports success while Gitea stores the result in a format its v4-only artifact API will never serve back. 72 artifacts on this repo are on disk with valid DB rows and are invisible to the REST API, the web download route and the MCP tools alike. Green jobs producing nothing retrievable — the dangerous failure mode, not a red build.actions/upload-artifact@v4+is not the fix: itsisGhes()rejects any hostname that isn'tgithub.com/*.ghe.com/*.localhostand throws before opening a connection, so no server-side change reaches it.What changed
android.ymlupload-artifactmirror @cb8afe72(v5)release.ymlminstrel-apkupload-artifactmirror @cb8afe72(v5)release.ymlminstrel-apkdownload-artifactmirror @8d4e9521(v6)Both point at pull mirrors of the Forgejo project's forks, which disable the client-side GHES refusal. Pinned by SHA because the mirrors auto-sync every 8h.
This repo consumes artifacts as well as producing them —
image-releasedownloadsminstrel-apkto bundle the APK into the container. Swapping only the upload would have left a v3 downloader reading the v1/v3 listing against a v4-protocol artifact: an empty result, not an error. So the download side needed a mirror too (bvandeusen/download-artifact, created for this).Pin selection matches on
@actions/artifact, not on the actions' version numbers — the two actions release on unrelated cadences, so upload v5 (^4.0.0) pairs with download v6 (^4.0.0), not download v5 (^2.3.2).^4.0.0is the library major just proven end to end on thoughtsync. Deliberately not download v7: that major moves tonode24and requires Actions Runner >= 2.327.1, which act_runner makes no claim to satisfy.Uploads also set
if-no-files-found: errorso an upload that matches nothing fails its own job instead of failing the consumer later.ci-requirements.mdcarries the full reasoning plus a version/runtime table.Verification
The same swap is proven end to end on thoughtsync (run 3094): both artifacts listed via
GET …/actions/runs/{id}/artifacts, downloaded from…/artifacts/{id}/zip, extracted, payloads genuine.Merging here is what exercises it on this repo —
android.yml's upload step is gated tomainpushes, sodevcannot prove it. I'll verify the APK artifact lists and downloads once themainrun completes.Known gap:
release.yml's upload+download pair is gated tov*tags, so it ships correct-by-analysis but unexercised until the next release.🤖 Generated with Claude Code
android.yml and release.yml uploaded via actions/upload-artifact@v3, which reports success while Gitea stores the result in a format its v4-only artifact API will never serve back — 72 artifacts on this repo are on disk, have valid DB rows, and are invisible to every retrieval path. Green jobs producing nothing retrievable. release.yml is a producer/consumer pair: android-release uploads minstrel-apk and image-release downloads it to bundle into the container. Swapping only the upload would have left download-artifact@v3 reading the v1/v3 listing and finding nothing, so mirror the download side too — bvandeusen/download-artifact, pull mirror of forgejo/download-artifact, pinned at its v5 tag to match the upload pin's major. Not actions/{upload,download}-artifact@v4: isGhes() throws on the hostname before opening a connection, so no server-side change reaches it. Upload steps also set if-no-files-found: error — image-release hard-depends on minstrel-apk existing, so an empty upload must fail where it happens. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>