Repository navigation
S4: CI cutover — release.yml builds from materialized tree, retire fork workflows (fork-absorption Phase 3) #1096
Description
Sub-issue of #1091 (Phase 3). S3 (#1095) is closed — cross-compilation is proven. This is the riskiest slice — the actual cutover.
Important
Problem — release.yml, ci.yml, prepare-release-candidate.yml, and overlay-promotion-bot.yml all depend on the fork repo for binary provisioning and provenance verification. After S1-S3 land, the binary can be built from the overlay, but CI still downloads from the fork.
Approach — Replace the fork-download steps with a single build-binary CI job that compiles the binary once and shares the artifact to all consumers via actions/upload-artifact / actions/download-artifact. Rewrite provenance checks against the overlay's version and base SHA (not the fork's repo/tag/ref). Retire fork-only workflows. Remove fork-specific CI secrets last, as a separate manual step after the alpha validates. The ordering is explicit: rewrite provenance gates first, retire fork workflows second, remove secrets last.
Build strategy — S3 proved cross-compilation works. S4 uses a single runner with OPENCODE_BUILD_TARGETS to produce all platform binaries in one job, with a downstream boot-smoke matrix for native execution verification (the fork's existing pattern).
Scope — in: release.yml rewrite (build from overlay, not download from fork); dedicated build-binary CI job with artifact sharing; ci.yml updates (all 5+ jobs that call fetch:opencode gain a needs: build-binary dependency instead); app-bundle-gate provenance check rewrite (verify lock.version against baked OPENCODE_VERSION + upstream_base_sha against materialized base); .buildinfo sidecar for channel assertion; package script update; prepare-release-candidate.yml retirement; overlay-promotion-bot.yml retirement; OPENCODE_FETCH_TOKEN removal; REPO_ACCESS_TOKEN removal (after verifying no remaining consumers); alpha release validation. · out: infrastructure retirement (S6); fork archival (S7).
Design decisions
Build artifact sharing (avoids 7x CI time multiplier)
The binary is built once in a dedicated build-binary CI job. All binary-consuming jobs (fast, vsix-gate, app-shelf-boot-proof, boot-smoke, release packaging) declare needs: build-binary and retrieve the artifact via actions/download-artifact. This avoids 7 jobs each compiling independently (7 × 2-5 min = 14-35 min wasted CI time).
Pin verification replacement
The fork-era lock.repo && lock.tag && lock.ref provenance check is replaced by two checks:
lock.versionmatches theOPENCODE_VERSIONstring baked into the compiled binary (the binary already embeds this at build time).- The manifest's
upstream_base_shamatches the base commit that was actually materialized into the overlay tree.
These are strictly local checks — no fork repo access required.
Channel assertion mechanism
The build:binary script records OPENCODE_CHANNEL into a .buildinfo sidecar file (plain text, key=value) alongside the compiled binary. The post-build assert_ui_gate.sh assertion reads this sidecar — simple, deterministic, no grepping minified code.
Acceptance Criteria
- A dedicated
build-binaryCI job compiles the binary once from the materialized overlay tree for all 3 platforms - All binary-consuming CI jobs (
fast,vsix-gate,app-shelf-boot-proof,boot-smoke, release packaging) useneeds: build-binary+actions/download-artifact— no job compiles independently -
release.ymlpackages binaries from the shared artifact — no download fromharmoniqs/opencode - The
packagescript invokesbuild:binaryinstead offetch_opencode.mjsfor binary provisioning - A
.buildinfosidecar is written alongside the binary recordingOPENCODE_CHANNEL;assert_ui_gate.shreads it to verifydevchannel - The
app-bundle-gateCI job verifies provenance:lock.versionmatches bakedOPENCODE_VERSION,upstream_base_shamatches the materialized base commit - Every CI job that called
fetch:opencodeinstead declaresneeds: build-binaryand downloads the shared artifact (with bun installed) - The
boot-smokematrix (native execution on 3 platforms) passes with the new binaries -
prepare-release-candidate.ymlandoverlay-promotion-bot.ymlare deleted -
promote.ymlstill dispatchesrelease.ymlcorrectly - An alpha release through the full pipeline succeeds on all platforms
- After the alpha validates:
OPENCODE_FETCH_TOKENis removed from CI secrets (separate manual step) - After the alpha validates:
REPO_ACCESS_TOKENis removed after verifying no remaining consumers (separate manual step)
Rollback path
Code revert and secret removal are separate, sequential steps — secret deletion is non-revertable.
- Code revert: One PR reverts the CI changes back to the fork-download path. The fork is still alive (not archived until S7) and the old
release.ymlis onegit revertaway. This is safe and repeatable. - Secret removal: Happens only after the alpha release validates successfully on all platforms. This is a separate manual step precisely because it cannot be undone — re-creating secrets requires regenerating tokens with the correct scopes.
The ordering is: land code changes → validate alpha → remove secrets. Never the reverse.
Obligations (from #1091 review)
- O1: Verify
promote.ymlcompatibility with the newrelease.yml - O2: Keep
boot-smokematrix as native execution verification - O4: Update the
packagescript's internalfetch_opencode.mjscall - O7: Specify how the lock-file pin verification step transitions between S4 and S6 — S4 replaces the fork-era check with the
lock.version+upstream_base_shaverification described above; S6 restructures the lock file format itself
Risks
- Cross-compiled binaries may behave differently than natively-built ones — the
boot-smokematrix (native execution, not just compilation) is the safety net. - CI time: compiling once is slower than downloading a pre-built binary, but the single-job + artifact-sharing design keeps the increase to one compile (~2-5 min) rather than seven.
- The
packagescript chains multiple steps; updating one link may break downstream steps. - Secret removal is irreversible — the explicit ordering (code first, secrets last, after alpha validation) is the mitigation.
Part of #1091. Answers #843 (retire fork provisioning, replace with overlay build).
Activity
jeonghun-jj-lee commentedon Sep 13, 2026
Adversarial review (round 1) —
blocking4 blocking, 5 advisory.
Blocking
git revert" but CI secret deletion (OPENCODE_FETCH_TOKEN,REPO_ACCESS_TOKEN) is non-revertable. Reverting code after deleting secrets leaves restored workflows with empty secret values.fetch:opencode(~20-30s each). Replacing each withbuild:binary(2-5 min) gives 14-35 min additional CI time.bun installcaching does not cache the compile step.actions/upload-artifact, addneeds: build-binaryto consumers. Or accept the 7x multiplier with documented rationale.release.ymlpin verification (lock.repo && lock.tag && lock.ref) becomes vestigial after S4 but is not removed or replaced until S6. O7 is acknowledged but unanswered.lock.versionmatchesOPENCODE_VERSIONand overlay manifest base SHA matches materialized tree).OPENCODE_CHANNEL=dev" has no specified mechanism. The currentassert_ui_gate.shchecks version +auth_tokenstring, not channel. Channel is a build-time Vite define, minified in the binary..channelsidecar,opencode --channelflag, or.buildinfofile. Make it an explicit design note.Advisory
app-bundle-gaterewrite lacks a specified replacement provenance mechanism.boot-smokestrategy branches on S3 finding — but S3 is already answered (cross-compile works).OPENCODE_FETCH_TOKENremoval must follow gate rewrites. Ordering constraint should be explicit.packagescript change makes bun a local dev dependency — contributor-facing regression.feat(ci): build binary from overlay, retire fork workflows (#1096)
feat(ci): build binary from overlay, retire fork workflows (#1096)
feat(ci): build binary from overlay, retire fork workflows (#1096)
feat(ci): build binary from overlay, retire fork workflows (#1096)
Merge pull request #1111 from harmoniqs/jj/1096-ci-cutover