fix(mobile): make EAS release submission deterministic - #356
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
vercel
Bot
temporarily deployed
to
Preview – zap-engine-control-center
September 2, 2026 08:12
Inactive
vercel
Bot
temporarily deployed
to
Preview – zap-engine-control-center
September 2, 2026 08:13
Inactive
vercel
Bot
temporarily deployed
to
Preview – zap-engine-control-center
September 2, 2026 08:13
Inactive
vercel
Bot
temporarily deployed
to
Preview – zap-engine-control-center
September 2, 2026 08:15
Inactive
vercel
Bot
temporarily deployed
to
Preview – zap-engine-control-center
September 2, 2026 08:15
Inactive
vercel
Bot
temporarily deployed
to
Preview – zap-engine-control-center
September 2, 2026 08:16
Inactive
i-xtsu-sixyou-ken-mei
force-pushed
the
fix/ios-release-exact-build-id
branch
from
September 2, 2026 14:30
50160e5 to
ce454ac
Compare
- eas.mjs: add runEasJson helper with raw-output tail on JSON parse failure - build-production.mjs: use runEasJson, require array output and status FINISHED (reject CANCELED), error on missing ID - submit-production-build.mjs: use runEasJson, restore WHY comments for numeric ascAppId and doc pointers - assert-ios-remote-version.mjs: use runEasJson, distinct message when buildNumber missing (not initialized -> ios:version:init) - tests: replace source-string checks with behavioral harness (stub pnpm logs calls, env-keyed JSON per subcommand, asserts exit/stdout/stderr/GITHUB_OUTPUT) - docs: explain how to locate EAS build ID via See logs URL and Expo dashboard - ios:release now chains ios:version:check preflight so local and CI are identical; remove redundant workflow step, keep ios:version:check standalone - format: apply prettier to satisfy quick-gates Co-Authored-By: internal-model
`pnpm <script> -- <arg>` forwards the literal `--` to the script instead of consuming it, so `android:submit -- "$BUILD_ID"` reached the wrapper as argv `[platform, '--', <id>]`. The wrapper read `--` as the build ID and dropped the ID the build job had just produced, which broke every build-and-submit run and every `submit-only` recovery. Pass the ID as a bare argument in the workflow and in the documented commands, reject an argument that cannot be a build ID, and cover both the wrapper guard and the workflow invocation with regression tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Prevent iOS releases from submitting stale build numbers or the wrong EAS binary, and make submission retries safe without consuming another store build number.
Context
The failed iOS release exposed two independent release-state problems:
2.1.0 (19)while EAS remote versioning was still in the single digits. The 2026-08-09 run produced build 5; its 2026-09-02 rerun produced build 6.submit-latest-production.mjsqueried EAS again for the latest finished production build. That identity lookup was unnecessary mutable global state and could race with builds started outside this workflow.GitHub reruns also rerun all steps in a failed build+submit job, so a submission-only failure could consume another build number.
Scope
submit-onlyrecovery mode.2.1.0 (19)).CANCELEDbuilds, simplifyresolveBuildto real EAS contract (array ofBuildFragment|null), addrunEasJsonhelper, distinct error for uninitialized remote.Out of scope
Product contract / invariants
Affected invariant:
apps/app/AGENTS.mdstore-release guardrails: remote auto-increment remains authoritative and submissions select the intended production/store build by ID.Why:
The implementation strengthens the existing invariant by carrying the exact build identity from build to submit instead of resolving global "latest" state.
Acceptance criteria
submit-onlyrequires an explicit platform build ID.eas build:list --limit 1oreas submit --latest.Implementation
build-production.mjsrunseas build --json --non-interactive, waits for completion, validates the returned array contains aFINISHEDbuild, extracts the exact ID, and publishes it to$GITHUB_OUTPUTwhen running in Actions.submit-production-build.mjsaccepts only an explicit EAS build ID, validates it viaeas build:view --json(platform/profile/distribution/status), and invokeseas submit --id <build-id>.The workflow now has separate
build-android,submit-android,build-ios, andsubmit-iosjobs. Build outputs are passed directly to submit jobs. Recovery mode acceptsandroid_build_id/ios_build_idinputs instead of resolving latest state.release-baselines.jsonrecords the known ASC floor of build 19 for iOS 2.1.0.assert-ios-remote-version.mjscompares that floor toeas build:version:get --jsonand fails fast when EAS is behind, with a distinct message whenbuildNumberis missing (not initialized).eas.mjsgainsrunEasJson(captures stdout +JSON.parsewith tail on failure) removing duplicated parse code.ios:releasenow chainsios:version:check && build-productionso local and CI use identical preflight; the separate workflowVerify EAS iOS build numberstep is removed.Docs note how to locate the exact EAS ID: last path segment of the
See logs:URL printed when the build starts (visible even on runner timeout) and on the Expo dashboard.Contract alignment
AGENTS.mdremains accurate.Validation
Local (worktree
fix/ios-release-exact-build-id@f76d26f7):EAS CLI contract checked against the pinned
eas-cli@20.5.1sources rather than prose:eas build --jsonprints an array of build objects and routes every other message to stderr,
build:viewexposes only--json(so--non-interactivemust not be injected),build:version:get --jsonreturnsbuildNumber,and
pnpm dlxsends its install progress to stderr — so capturing stdout for JSON is safe on a cold cache.Not run locally:
test:ios:release-smoke(41 min; CI ran it green on13bdc426and no native/dependencyconfiguration changed since). Real EAS calls cannot be exercised locally.
Defect found in review and fixed (
f76d26f7)pnpm <script> -- <arg>forwards the literal--to the script instead of consuming it. Verified againstpnpm 10.30.3 for both
pnpm --filter <pkg> <script> -- <arg>and the explicitrunform:So
pnpm --filter @zapengine/app android:submit -- "$BUILD_ID"reached the wrapper with--as the buildID and the real ID dropped, which broke every
build-and-submitrun and everysubmit-onlyrecovery.Reproduced against the wrapper with a stubbed EAS CLI: it printed
Submitting production ios build --.and issued
build:view -- --jsonplussubmit ... --id -- --non-interactive, exiting 0.Fixed by passing the ID as a bare argument in the workflow and in every documented command, rejecting an
argument that cannot be a build ID, and adding regression tests for the wrapper guard and the workflow
invocation.
Known unrelated failures
None outstanding. Full CI was green on
13bdc426. The earlier red run wasquick-gates(prettier drift inthree PR files, fixed) and
code-quality(@zapengine/control-center#dup:check, on a run merged onto thestale base
f5522543; this PR touches nocontrol-centerfile).Implementation note — preflight enforcement
apps/app/package.jsonios:releasenow runsnode scripts/assert-ios-remote-version.mjs && node scripts/build-production.mjs ios. The separateVerify EAS iOS build number is not behind App Store Connectstep in.github/workflows/release-mobile.yml(build-iosjob) is removed, so a local release and CI enforce the same floor.ios:version:checkremains as a standalone diagnostic. README andapps/app/docs/ios-release.mdreflect this.Reviewer notes
Do not solve the remaining one-time version alignment by calling Expo's undocumented
createAppVersionGraphQL mutation from CI. EAS CLI's supportedbuild:version:setcommand is interactive; after the remote value is set to the ASC high-water mark once, ordinary EAS-only releases remain monotonic.