From 4b7ab46d5cc530b08f42b72a7ba36a83c197c49a Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 22 Jul 2026 15:28:17 +0300 Subject: [PATCH] eas: advance OTA fingerprint baseline via repo variable The OTA pipeline decided OTA-vs-native-rebuild by diffing the current fingerprint against a baseline commit stored in an actions/cache entry keyed most-recent-testflight-commit. That cache only saved on a miss but was restored on every run, so it only advanced when GitHub evicted it. Once eviction stopped (~June 12), the baseline froze, every fingerprint looked changed, and OTA deploys silently stopped - every push ran full native builds instead. Replace the cache baseline with a repo variable (MOST_RECENT_TESTFLIGHT_NATIVE_HASH) advanced by a dedicated recordBaseline job that runs only after both native builds succeed. The fingerprint action (bumped to the hash-aware v0.3.0) compares the current commit's native hash against the variable directly, skipping the baseline checkout+reinstall. recordBaseline is isolated so the PAT that can write repo variables (EAS_BASELINE_VARIABLE_TOKEN - the built-in GITHUB_TOKEN cannot manage variables) lives nowhere else in the pipeline, and refuses to write an empty value. The now-dead cache write steps are removed from the reusable build-submit-ios/android workflows. Co-Authored-By: Claude Fable 5 --- .github/workflows/build-submit-android.yml | 16 ----- .github/workflows/build-submit-ios.yml | 16 ----- .../workflows/bundle-deploy-eas-update.yml | 68 +++++++++++++++---- 3 files changed, 53 insertions(+), 47 deletions(-) diff --git a/.github/workflows/build-submit-android.yml b/.github/workflows/build-submit-android.yml index a2261536e6..52bd28caf6 100644 --- a/.github/workflows/build-submit-android.yml +++ b/.github/workflows/build-submit-android.yml @@ -170,22 +170,6 @@ jobs: payload: | {"text": "Android ${{ inputs.profile || 'testflight-android' }} build submitted to Google Play!\n```Version Number: ${{ needs.build.outputs.package-version }}\nBuild Number: ${{ needs.build.outputs.version-code }}```"} - # Record the commit only after a successful submit, so a failed submit doesn't - # advance the "most recent testflight" marker. - - name: ⬇️ Restore Cache - id: get-base-commit - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - if: ${{ inputs.profile == 'testflight-android' }} - with: - path: most-recent-testflight-commit.txt - key: most-recent-testflight-commit - - - name: ✏️ Write commit hash to cache - if: ${{ inputs.profile == 'testflight-android' }} - env: - GITHUB_SHA: ${{ github.sha }} - run: echo $GITHUB_SHA > most-recent-testflight-commit.txt - # Runs in parallel with submit: the QA APK shouldn't be blocked by a Play submission failure. universalApk: name: Build universal APK diff --git a/.github/workflows/build-submit-ios.yml b/.github/workflows/build-submit-ios.yml index 2085aabaa4..a1c6b45ec8 100644 --- a/.github/workflows/build-submit-ios.yml +++ b/.github/workflows/build-submit-ios.yml @@ -254,22 +254,6 @@ jobs: payload: | {"text": "iOS production build for App Store submission is ready!\n```Artifact: Check TestFlight to know when it is available\nVersion Number: ${{ needs.build.outputs.package-version }}\nBuild Number: ${{ needs.build.outputs.build-number }}```"} - # Record the commit only after a successful submit, so a failed submit doesn't advance - # the baseline used for the next testflight build's changelog. - - name: ⬇️ Restore Cache - id: get-base-commit - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - if: ${{ inputs.profile == 'testflight' }} - with: - path: most-recent-testflight-commit.txt - key: most-recent-testflight-commit - - - name: ✏️ Write commit hash to cache - env: - GITHUB_SHA: ${{ github.sha }} - if: ${{ inputs.profile == 'testflight' }} - run: echo $GITHUB_SHA > most-recent-testflight-commit.txt - distribute: name: Assign build to TestFlight group # fastlane and jq ship preinstalled on the macOS runner image, and this step mostly idles diff --git a/.github/workflows/bundle-deploy-eas-update.yml b/.github/workflows/bundle-deploy-eas-update.yml index 9154d90040..7d6a23b3fc 100644 --- a/.github/workflows/bundle-deploy-eas-update.yml +++ b/.github/workflows/bundle-deploy-eas-update.yml @@ -44,6 +44,9 @@ jobs: # A version bump forces a native build even if the fingerprint is unchanged changes-detected: ${{ steps.fingerprint.outputs.includes-changes || steps.version.outputs.version-changed }} + # Native-autolinking hash of this commit. recordBaseline persists it as the + # next baseline once both native builds have shipped this native surface. + native-hash: ${{ steps.fingerprint.outputs.current-native-hash }} steps: - name: Check for EXPO_TOKEN @@ -103,10 +106,15 @@ jobs: - name: 📷 Check fingerprint and install dependencies id: fingerprint - uses: bluesky-social/github-actions/fingerprint-native@b5556913e4aef3964cfd5936d0add3fc0d809bdb # v0.2.0 + uses: bluesky-social/github-actions/fingerprint-native@c816fb4e387f8d53b8c81ec20b4cabdee8840d00 # v0.3.0 with: profile: ${{ inputs.channel || 'testflight' }} previous-commit-tag: ${{ inputs.runtimeVersion }} + # Baseline is a repo variable advanced by the recordBaseline job after + # each successful native deploy, instead of a frozen actions/cache entry. + # Empty on the very first run, in which case the action falls back to the + # legacy cache baseline. + baseline-native-hash: ${{ vars.MOST_RECENT_TESTFLIGHT_NATIVE_HASH }} - name: 🔤 Compile translations uses: ./.github/actions/compile-i18n @@ -195,20 +203,6 @@ jobs: RUNTIME_VERSION: ${{ inputs.runtimeVersion }} CHANNEL_NAME: ${{ inputs.channel || 'testflight' }} - - name: ⬇️ Restore Cache - id: get-base-commit - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - if: ${{ !steps.fingerprint.outputs.includes-changes && - !steps.version.outputs.version-changed }} - with: - path: most-recent-testflight-commit.txt - key: most-recent-testflight-commit - - - name: ✏️ Write commit hash to cache - if: ${{ !steps.fingerprint.outputs.includes-changes && - !steps.version.outputs.version-changed }} - run: echo $GITHUB_SHA > most-recent-testflight-commit.txt - buildIfNecessaryIOS: name: Build and Submit iOS needs: [bundleDeploy] @@ -270,3 +264,47 @@ jobs: ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} + + # Advance the fingerprint baseline only after BOTH native builds have shipped + # the new native surface. This replaces the old actions/cache baseline, which + # only advanced on cache eviction and so silently froze - freezing meant every + # fingerprint looked changed and OTA updates stopped deploying entirely. + # + # This runs only on the native-build path (both build jobs succeeded). On the + # OTA path those jobs are skipped, so this job is skipped too - correct, since + # an OTA update by definition leaves the native surface (and thus the hash) + # unchanged, so there is nothing to advance. + # + # Isolated as its own job so the token that can write repo variables lives + # nowhere else in the pipeline. The built-in GITHUB_TOKEN cannot manage Actions + # variables under any `permissions:` setting, so a PAT/App token with + # `variables: write` is required (EAS_BASELINE_VARIABLE_TOKEN). + recordBaseline: + name: Record fingerprint baseline + runs-on: ubuntu-latest + needs: [bundleDeploy, buildIfNecessaryIOS, buildIfNecessaryAndroid] + if: ${{ (inputs.channel || 'testflight') == 'testflight' && + needs.buildIfNecessaryIOS.result == 'success' && + needs.buildIfNecessaryAndroid.result == 'success' && + github.repository == 'bluesky-social/social-app' }} + # No repo checkout or GITHUB_TOKEN work happens here; only the PAT is used. + permissions: {} + steps: + - name: ✏️ Advance baseline repo variable + env: + # PAT/App token with `variables: write`; see the job comment above + GH_TOKEN: ${{ secrets.EAS_BASELINE_VARIABLE_TOKEN }} + REPO: ${{ github.repository }} + NATIVE_HASH: ${{ needs.bundleDeploy.outputs.native-hash }} + run: | + # Fail loudly rather than writing an empty baseline. A blank value here + # would make the next run's fast-path comparison always mismatch and + # force perpetual native builds - the exact silent failure we're fixing. + if [ -z "$NATIVE_HASH" ]; then + echo "::error::bundleDeploy did not emit a native hash; refusing to write an empty baseline." + exit 1 + fi + echo "Advancing MOST_RECENT_TESTFLIGHT_NATIVE_HASH to $NATIVE_HASH" + gh variable set MOST_RECENT_TESTFLIGHT_NATIVE_HASH \ + --repo "$REPO" \ + --body "$NATIVE_HASH"