You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Alpha 5 delivers first-class macOS support and launches the community's native BGFX Zero Hour client against the user's retail data — no Wine. It lands the four-phase feat/macos-host → feat/native-launch stack plus the hardening items from its external review.
Notarization (#322) is not being pursued. It requires a Developer ID certificate,
which requires a paid Apple Developer Program membership, and that is not being
purchased. #354 fixes the half that needed no certificate: GenHub clears com.apple.quarantine from the game executables it materializes, so the propagation
failure — GenHub opens, the game silently will not launch — no longer happens. What
remains is one documented step for the app bundle itself
(xattr -dr com.apple.quarantine /Applications/GenHub.app).
macOS is therefore installable with one manual step, not distributable in the
Gatekeeper sense. The signing pipeline is built and verified under an ad-hoc identity
on feat/macos-signing-notarization, unmerged, ready if that decision changes.
Scope
Definition of done: the three PRs land and #354 lands — at that point macOS is
installable with the one documented step. Notarization is explicitly out of scope; see #322. Priority order; "PR" is which stack PR
closes it (see The mac stack below).
#
Issue
Closed by
1
#314 — Reject multi-variant manifests at ingestion
PR A
2
#315 — Validate retail archive roots before launch
The three stack PRs have all landed. PR A merged as #328, PR B as #329 (plus #331 for the service registrations it needed), and PR C as #332. The remaining
definition-of-done item is #322 notarization.
#315, #317 and #318 shipped inside #332 exactly as the note below predicts, and #316, #320 and #325 shipped as #338, #337 and #339. None auto-closed, so all six
were closed manually on 2026-08-03 with the merged PR and file evidence recorded on
each issue.
#333 is the one item no amount of GenHub work can close. The engine skips the
base-Generals load silently in release builds — DEBUG_ASSERTCRASH is debug-only,
and the in-tree @todo says it ought to be a release crash. GenHub cannot detect
the case from the filesystem: a Zero Hour workspace always contains *.big, so a
workspace archive check would always pass and guarantee nothing. It needs either the
engine to make the empty-installPath case observable, or a mount-count signal
GenHub can act on. Until one exists, #333 stays open regardless of milestone
progress.
#314–#319 are review findings on code that has not shipped. Their fixes are
folded into the owning branch before the PR opens, so each will close without a
visible "fixes #NNN" diff — the code reads as originally written. Each issue
carries a comment saying which PR closes it. They are kept as issues rather than PR
checklist items because they describe user-visible behaviour worth finding later.
Restructured from four sequential PRs into two independent PRs plus one dependent
one, and rebased onto development @ 8e07339. All three have since merged.
A and B were reviewed in parallel as intended, and C followed them.
Why this beats the original four-deep chain
The original plan chained macos-host → platform-neutral-model → unix-filesystem → native-launch. Measured against this repo's rules, that chain
is expensive: squash-merging a parent orphans its child (it keeps the parent's
unsquashed commits and goes CONFLICTING, needing a manual retarget and rebase),
and dismiss_stale_reviews_on_push: true means every such rebase drops the
approvals. A four-deep stack pays that three times. We hit exactly this with #305/#309 during the Alpha 4 blockers.
The two middle phases fix real Linux and Windows bugs — silent hard-link copy,
Windows paths on Linux, updater installing the wrong platform, symlink reachability
— and are not macOS work. Checking the actual coupling: of 43 files they touch,
only two tie them to the macOS layer, and the types they introduce
(MacOSGamePathProvider, UnixFileOperationsService, UnixSymlinkCapabilityProvider)
all live in the shared project, not GenHub.MacOS. The only genuinely
macOS-dependent parts are three DI registration lines plus two entries in a shared
test-assertion helper that the macOS branch creates.
Those five lines moved into a single wiring commit at the head of C, which is the
first point where both the types and the module exist. That makes A fully
standalone: 7 commits, zero GenHub.MacOS files, 1384 tests green directly on development. C's tree is byte-identical to the original four-deep stack, so
nothing was lost or gained — 1395 tests green.
The practical win: the cross-platform bug fixes stop being hostage to macOS review,
and two of the three PRs can be reviewed at the same time.
Landing notes
The stack must be rebased once more after chore: alpha 4 release #229 merges.main requires
linear history, so chore: alpha 4 release #229 lands as a squash or rebase — both mint new SHAs, and 8e07339 will not be an ancestor of main. The rebase already done resolved
both collisions against current code, so the second pass should be mechanical.
Two collisions were found and are already resolved in the branches:
#331 was not planned.#328 made IGamePathProvider a required dependency and #329
added the macOS DI module, but the macOS registrations reference types from the first and a
module from the second, so they could only compile once both were in. Each PR was green in
isolation and the merged state was not: GenHub.app aborted at container build and development went red. #331 repaired it and now runs GenHub.Tests.MacOS before publish,
where the assertion had existed but sat after the smoke test and never executed.
The lesson for the remaining independent PRs: two green branches can merge into a broken
state, and no per-branch CI can see it.
Their fixes are merged — #328 carries #314 and #316, #329 carries #319 — and the Fixes
links are recorded. GitHub only auto-closes on merge to the default branch, and these
merged into development, so they will close when development reaches main at release.
They are done, not forgotten.
Detect engine archive failures after spawn: 500 ms window and unused [ggc] stderr sentinel #334 — GenHub's own half. The engine does report the other failures (exit 1 on both
upstream and fork; the [ggc] ARCHIVE MOUNT FAILED stderr sentinel is fork-only, absent
upstream on every platform), and GenHub ignores both: its immediate-exit check is a 500 ms
window and nothing matches the sentinel it already captures.
A premise correction worth recording.#315, #324 and this issue were written on the
belief that the engine reaches its main loop with zero content and reports nothing. Verified
against bobtista/GeneralsGameCode at bobtista/topic/trunk, that is false — a bad root
aborts during initialisation with exit 1 and a ReleaseCrashInfo.txt, and an unmountable
archive also writes to stderr. Neither reaches the main loop. #324's rationale should be
re-read in that light: it is still worth doing, but as an assertion on exit code and
sentinel rather than on the process merely staying alive.
Proposal
Alpha 5 delivers first-class macOS support and launches the community's native BGFX Zero Hour client against the user's retail data — no Wine. It lands the four-phase
feat/macos-host→feat/native-launchstack plus the hardening items from its external review.Notarization (#322) is not being pursued. It requires a Developer ID certificate,
which requires a paid Apple Developer Program membership, and that is not being
purchased. #354 fixes the half that needed no certificate: GenHub clears
com.apple.quarantinefrom the game executables it materializes, so the propagationfailure — GenHub opens, the game silently will not launch — no longer happens. What
remains is one documented step for the app bundle itself
(
xattr -dr com.apple.quarantine /Applications/GenHub.app).macOS is therefore installable with one manual step, not distributable in the
Gatekeeper sense. The signing pipeline is built and verified under an ad-hoc identity
on
feat/macos-signing-notarization, unmerged, ready if that decision changes.Scope
Definition of done: the three PRs land and #354 lands — at that point macOS is
installable with the one documented step. Notarization is explicitly out of scope; see
#322. Priority order; "PR" is which stack PR
closes it (see The mac stack below).
DYLD_LIBRARY_PATH/LD_LIBRARY_PATHValidateScopesshrink-only ratchetmacos-15CI[ggc]sentinelCurrent status — updated 2026-08-03
The three stack PRs have all landed. PR A merged as #328, PR B as #329 (plus
#331 for the service registrations it needed), and PR C as #332. The remaining
definition-of-done item is #322 notarization.
development, PRs to follow#315, #317 and #318 shipped inside #332 exactly as the note below predicts, and
#316, #320 and #325 shipped as #338, #337 and #339. None auto-closed, so all six
were closed manually on 2026-08-03 with the merged PR and file evidence recorded on
each issue.
#333 is the one item no amount of GenHub work can close. The engine skips the
base-Generals load silently in release builds —
DEBUG_ASSERTCRASHis debug-only,and the in-tree
@todosays it ought to be a release crash. GenHub cannot detectthe case from the filesystem: a Zero Hour workspace always contains
*.big, so aworkspace archive check would always pass and guarantee nothing. It needs either the
engine to make the empty-
installPathcase observable, or a mount-count signalGenHub can act on. Until one exists, #333 stays open regardless of milestone
progress.
#314–#319 are review findings on code that has not shipped. Their fixes are
folded into the owning branch before the PR opens, so each will close without a
visible "fixes #NNN" diff — the code reads as originally written. Each issue
carries a comment saying which PR closes it. They are kept as issues rather than PR
checklist items because they describe user-visible behaviour worth finding later.
Moved out of this milestone:
.Files→ResolvedArtifact) → 7. Content Pipeline & Distribution.A large migration across deliverers, validators, CAS refcounting and GC. Adjacent
to Alpha 5 — the mac stack introduces manifest variants and Reject multi-variant manifests at ingestion until .Files consumers are migrated #314 gates them — but
not required to ship native macOS, and big enough to dominate the milestone.
Reject multi-variant manifests at ingestion until .Files consumers are migrated #314's fail-closed gate must stay until Migrate .Files consumers to the resolved-variant model (ResolvedArtifact) #321 lands.
supply-chain hygiene, explicitly low priority, unrelated to macOS.
Dependencies worth knowing
.Files→ResolvedArtifactmigration lands, the ingestion gate is the only protection against a variant manifest corrupting CAS refcounting and GC. Reject multi-variant manifests at ingestion until .Files consumers are migrated #314 must land before any provider can publish aVariantsmanifest.DYLD_LIBRARY_PATH, so behaviour would change silently at notarization time if the variable is still in use.chmodwindow permanently bricks a workspace, because verification never mutates.The mac stack
Restructured from four sequential PRs into two independent PRs plus one dependent
one, and rebased onto
development@8e07339. All three have since merged.feat/cross-platform-fixesdevelopmentfeat/macos-hostdevelopmentfeat/native-launchA and B were reviewed in parallel as intended, and C followed them.
Why this beats the original four-deep chain
The original plan chained
macos-host→platform-neutral-model→unix-filesystem→native-launch. Measured against this repo's rules, that chainis expensive: squash-merging a parent orphans its child (it keeps the parent's
unsquashed commits and goes
CONFLICTING, needing a manual retarget and rebase),and
dismiss_stale_reviews_on_push: truemeans every such rebase drops theapprovals. A four-deep stack pays that three times. We hit exactly this with
#305/#309 during the Alpha 4 blockers.
The two middle phases fix real Linux and Windows bugs — silent hard-link copy,
Windows paths on Linux, updater installing the wrong platform, symlink reachability
— and are not macOS work. Checking the actual coupling: of 43 files they touch,
only two tie them to the macOS layer, and the types they introduce
(
MacOSGamePathProvider,UnixFileOperationsService,UnixSymlinkCapabilityProvider)all live in the shared project, not
GenHub.MacOS. The only genuinelymacOS-dependent parts are three DI registration lines plus two entries in a shared
test-assertion helper that the macOS branch creates.
Those five lines moved into a single wiring commit at the head of C, which is the
first point where both the types and the module exist. That makes A fully
standalone: 7 commits, zero
GenHub.MacOSfiles, 1384 tests green directly ondevelopment. C's tree is byte-identical to the original four-deep stack, sonothing was lost or gained — 1395 tests green.
The practical win: the cross-platform bug fixes stop being hostage to macOS review,
and two of the three PRs can be reviewed at the same time.
Landing notes
The stack must be rebased once more after chore: alpha 4 release #229 merges.
mainrequireslinear history, so chore: alpha 4 release #229 lands as a squash or rebase — both mint new SHAs, and
8e07339will not be an ancestor ofmain. The rebase already done resolvedboth collisions against current code, so the second pass should be mechanical.
Two collisions were found and are already resolved in the branches:
SharedViewModelModule/IGitHubTokenStorage(fix(linux): resolve optional token storage at startup #301) — anticipated, andtrivial:
sp.GetService<IGitHubTokenStorage>()is byte-identical on bothsides, since fix(linux): resolve optional token storage at startup #301 made the same fix. Only the stack's explanatory comment
conflicted; it was kept, as fix(linux): resolve optional token storage at startup #301's version has none.
ManifestDiscoveryServiceconstructor (fix(manifest): use JSON file pattern during discovery #305/fix(manifest): continue past unavailable directories #309) — not anticipated, andthe dangerous one. The stack adds
IConfigurationProviderServiceto theconstructor; fix(manifest): use JSON file pattern during discovery #305/fix(manifest): continue past unavailable directories #309 added filesystem test seams to the same constructor.
Git auto-merges the production file with no conflict and produces code
that does not compile (
CS1729) — only the test file conflicts, so amechanical resolution looks clean and then fails to build. Resolved by
collapsing the two constructors into one with the seams as optional
parameters, so any future dependency causes a compile error at every call
site instead of a silent broken chain.
Fold the review fixes into the owning branch before opening PRs
(Reject multi-variant manifests at ingestion until .Files consumers are migrated #314–macOS installation detector must distinguish TCC denial from no installations found #319 map to PRs as noted in the priority table above).
Open A and B together; open C after both land.
Say in A's description that it fixes Linux and Windows bugs and stands on its own
merits — it is not macOS-only work.
Engine-repo track
Tracked separately in the engine repository (
bobtista/GeneralsGameCode,topic/trunk, upstream TheSuperHackers):CNC_ZH_USERDATAPATHfor per-profileOptions.iniGlobalData.cppto always append the separatorThe third is the engine-side half of #315 and would simplify every GenHub-side mitigation.
Status
Alpha 5 opens once Alpha 4 ships. Alpha 4 readiness is tracked in #307.
The stack, as it actually landed
#331 was not planned. #328 made
IGamePathProvidera required dependency and #329added the macOS DI module, but the macOS registrations reference types from the first and a
module from the second, so they could only compile once both were in. Each PR was green in
isolation and the merged state was not:
GenHub.appaborted at container build anddevelopmentwent red. #331 repaired it and now runsGenHub.Tests.MacOSbefore publish,where the assertion had existed but sat after the smoke test and never executed.
The lesson for the remaining independent PRs: two green branches can merge into a broken
state, and no per-branch CI can see it.
Why #314, #316 and #319 are still open
Their fixes are merged — #328 carries #314 and #316, #329 carries #319 — and the
Fixeslinks are recorded. GitHub only auto-closes on merge to the default branch, and these
merged into
development, so they will close whendevelopmentreachesmainat release.They are done, not forgotten.
#332 will close #315, #317 and #318 on the same terms.
Scope added after this issue was written
.bigexistence andshould share constants; feat(launching): launch the native BGFX client against retail archives #332 introduced
RetailArchiveConstants.ArchiveSearchPatternforexactly that. It defers the
*.exeglob to Classify executables by magic bytes instead of the extensionless heuristic #325 rather than adding a second heuristic, soit depends on Classify executables by magic bytes instead of the extensionless heuristic #325 landing first for that part.
engine: an empty Generals path skips the base load silently in release, guarded only by a
debug assert carrying a
@todothat says it should be a release crash.upstream and fork; the
[ggc] ARCHIVE MOUNT FAILEDstderr sentinel is fork-only, absentupstream on every platform), and GenHub ignores both: its immediate-exit check is a 500 ms
window and nothing matches the sentinel it already captures.
A premise correction worth recording. #315, #324 and this issue were written on the
belief that the engine reaches its main loop with zero content and reports nothing. Verified
against
bobtista/GeneralsGameCodeatbobtista/topic/trunk, that is false — a bad rootaborts during initialisation with exit 1 and a
ReleaseCrashInfo.txt, and an unmountablearchive also writes to stderr. Neither reaches the main loop. #324's rationale should be
re-read in that light: it is still worth doing, but as an assertion on exit code and
sentinel rather than on the process merely staying alive.