Every CI run fails during build-core, so no base.zip is produced and no device-installable build can be made. Inherited from upstream; almost certainly failing there too. #17 is chipping away at it.
This turned out to be systemic rather than a single bad core.
Root cause
Cores are cloned unpinned and shallow, straight from third-party HEAD:
cd src && git clone --depth 1 $(if $($1_BRANCH),--branch $($1_BRANCH),) $$($(1)_REPO) $(1)
Then a local patch is applied to add the tg5040/tg5050 platform block. When the upstream repo changes that file, the patch stops applying and the build dies — with no commit on our side. Two have already rotted:
| Core |
Symptom |
Broken by |
Pinned to |
mgba |
Makefile.libretro: No such file or directory |
8940477 (2026-08-04) deleted it — CI moved to cmake |
925f0f0 |
fceumm |
patch failed: Makefile.libretro:461 — context drift |
8ed0cb2 (2026-07-23) replaced bundled zlib |
becde7dc |
Both verified by checking out the pinned commit and running git apply --check for the tg5040 and tg5050 patches, which differ per platform.
Why it kept looking like one core
fail-fast: true on the core matrix meant the first failure cancelled the other ~40 jobs, so each CI round surfaced exactly one broken core and fixing them was serial. #17 turns it off, so one run now reports every broken core at once.
Remaining work
Automating the bumps
Pinning trades "randomly broken" for "deliberately stale", and stale is only acceptable if bumping is easy. Worth a scheduled workflow that:
- for each core, resolves upstream HEAD
- tries applying our patch against it
- builds the ones that apply
- opens a PR bumping the pins that built green, listing the ones that didn't
That makes staleness a review queue rather than silent drift, and it catches upstream breakage on its schedule instead of when someone needs a release. Dependabot can't do arbitrary git pins, so this needs to be a small bespoke workflow — but it's mostly the loop above, and the patch-check step is cheap (a blobless clone plus git apply --check).
Cadence matters more for some cores than others. FBNeo is the one to watch: arcade romset compatibility is tightly version-coupled, so a stale pin there is user-visible in a way a stale gambatte is not. pcsx_rearmed sees active ARM performance work. The mature 8/16-bit cores can drift for a year with little cost.
Impact
Blocks #1, #2 and #3 from producing installable artifacts, and blocks any release. Not caused by any of them — the same failure occurs on main.
Every CI run fails during
build-core, so nobase.zipis produced and no device-installable build can be made. Inherited from upstream; almost certainly failing there too. #17 is chipping away at it.This turned out to be systemic rather than a single bad core.
Root cause
Cores are cloned unpinned and shallow, straight from third-party HEAD:
Then a local patch is applied to add the
tg5040/tg5050platform block. When the upstream repo changes that file, the patch stops applying and the build dies — with no commit on our side. Two have already rotted:mgbaMakefile.libretro: No such file or directory8940477(2026-08-04) deleted it — CI moved to cmake925f0f0fceummpatch failed: Makefile.libretro:461— context drift8ed0cb2(2026-07-23) replaced bundled zlibbecde7dcBoth verified by checking out the pinned commit and running
git apply --checkfor the tg5040 and tg5050 patches, which differ per platform.Why it kept looking like one core
fail-fast: trueon the core matrix meant the first failure cancelled the other ~40 jobs, so each CI round surfaced exactly one broken core and fixing them was serial. #17 turns it off, so one run now reports every broken core at once.Remaining work
mgba(fix(cores): pin mgba and fceumm, stop fail-fast hiding broken cores #17)fceumm(fix(cores): pin mgba and fceumm, stop fail-fast hiding broken cores #17)fail-fast: falseon the core matrix (fix(cores): pin mgba and fceumm, stop fail-fast hiding broken cores #17)Automating the bumps
Pinning trades "randomly broken" for "deliberately stale", and stale is only acceptable if bumping is easy. Worth a scheduled workflow that:
That makes staleness a review queue rather than silent drift, and it catches upstream breakage on its schedule instead of when someone needs a release. Dependabot can't do arbitrary git pins, so this needs to be a small bespoke workflow — but it's mostly the loop above, and the patch-check step is cheap (a blobless clone plus
git apply --check).Cadence matters more for some cores than others. FBNeo is the one to watch: arcade romset compatibility is tightly version-coupled, so a stale pin there is user-visible in a way a stale gambatte is not.
pcsx_rearmedsees active ARM performance work. The mature 8/16-bit cores can drift for a year with little cost.Impact
Blocks #1, #2 and #3 from producing installable artifacts, and blocks any release. Not caused by any of them — the same failure occurs on
main.