From 9a89c7aa03f4b3e9890acee8824cc0fe35fba5ee Mon Sep 17 00:00:00 2001 From: Amy Galles <9685081+AmyLGalles@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:05:18 -0700 Subject: [PATCH 1/2] fix: Automatically pin release candidate to Source Fastlane track Promoting internal -> production intermittently fails with "Cannot find release with version code" because Google's Play API only returns the latest release per track, and continuous internal builds supersede the candidate before someone runs the promotion workflow. There's an existing manual workaround (a "Source Fastlane" holding track) but nothing keeps it fresh, so it goes stale too. Pin the just-uploaded internal build onto Source Fastlane in the same CI run, immediately after upload, before any later build can supersede it. Reuses the existing promoteToProduction lane; adds an optional serviceCredentialsFile override needed for the Authenticator job, which doesn't have play_creds.json on disk. --- .github/workflows/build-authenticator.yml | 16 ++++++++++++++++ .github/workflows/build.yml | 16 ++++++++++++++++ fastlane/Fastfile | 8 ++++++++ 3 files changed, 40 insertions(+) diff --git a/.github/workflows/build-authenticator.yml b/.github/workflows/build-authenticator.yml index d0fbcbac27b..b2b076ff4dc 100644 --- a/.github/workflows/build-authenticator.yml +++ b/.github/workflows/build-authenticator.yml @@ -237,3 +237,19 @@ jobs: run: | bundle exec fastlane publishAuthenticatorReleaseToGooglePlayStore \ serviceCredentialsFile:"$PLAY_STORE_CREDS_FILE" \ + + - name: Pin release to Source Fastlane track + if: ${{ matrix.variant == 'aab' && env.PUBLISH_TO_PLAY_STORE == 'true' }} + env: + PLAY_STORE_CREDS_FILE: ${{ github.workspace }}/secrets/authenticator_play_store-creds.json + VERSION_CODE: ${{ needs.version.outputs.version_number || github.run_number }} + VERSION_NAME: ${{ needs.version.outputs.version_name }} + run: | + bundle exec fastlane promoteToProduction \ + packageName:"com.bitwarden.authenticator" \ + serviceCredentialsFile:"$PLAY_STORE_CREDS_FILE" \ + track:"internal" \ + trackPromoteTo:"Source Fastlane" \ + versionCode:"$VERSION_CODE" \ + versionName:"$VERSION_NAME" \ + rolloutPercentage:"1" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7af42e1c17d..ef50531183a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -334,6 +334,22 @@ jobs: bundle exec fastlane publishProdToPlayStore bundle exec fastlane publishBetaToPlayStore + - name: Pin release to Source Fastlane track + if: ${{ matrix.variant == 'prod' && matrix.artifact == 'aab' && env.PUBLISH_TO_PLAY_STORE == 'true' }} + env: + PLAY_STORE_CREDS_FILE: ${{ github.workspace }}/secrets/play_creds.json + VERSION_CODE: ${{ needs.version.outputs.version_number || github.run_number }} + VERSION_NAME: ${{ needs.version.outputs.version_name }} + run: | + bundle exec fastlane promoteToProduction \ + packageName:"com.x8bit.bitwarden" \ + serviceCredentialsFile:"$PLAY_STORE_CREDS_FILE" \ + track:"internal" \ + trackPromoteTo:"Source Fastlane" \ + versionCode:"$VERSION_CODE" \ + versionName:"$VERSION_NAME" \ + rolloutPercentage:"1" + publish_fdroid: name: Publish F-Droid artifacts needs: diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 0445aa82185..312b5bab8ee 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -570,6 +570,14 @@ platform :android do release_options[:version_name] = options[:versionName] end + # Only override json_key if an explicit credentials file is provided (e.g. when this + # lane is invoked for a package whose credentials aren't at the Appfile default path, + # such as com.bitwarden.authenticator). When omitted, supply() falls back to the + # Appfile default (secrets/play_creds.json), preserving existing behavior. + if options[:serviceCredentialsFile] + release_options[:json_key] = options[:serviceCredentialsFile] + end + if options[:releaseNotes].nil? or options[:releaseNotes].to_s.empty? release_options[:skip_upload_metadata] = true else From 0dc2e2a15a181781c7825e8a7101b1074b7b1aa6 Mon Sep 17 00:00:00 2001 From: Amy Galles <9685081+AmyLGalles@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:28:06 -0700 Subject: [PATCH 2/2] fix: Gate release pin to release branches, fix credentials fallback Code review caught two real gaps in the previous commit: - Pinning on every main push made Source Fastlane just as volatile as internal, since builds land on main continuously while a release candidate is cut on a dedicated release/-rcN branch and can sit for a while before someone promotes it. Gate the pin step to release/* branches so it only refreshes when a real RC is cut. - promoteToProduction's serviceCredentialsFile override had no package-name fallback like the sibling getLivePlayStoreVersion lane, so the real bitwarden/deploy caller (which never passes it) still resolved play_creds.json for the Authenticator package. --- .github/workflows/build-authenticator.yml | 7 ++++++- .github/workflows/build.yml | 7 ++++++- fastlane/Fastfile | 14 ++++++++++---- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-authenticator.yml b/.github/workflows/build-authenticator.yml index b2b076ff4dc..ab49c17e399 100644 --- a/.github/workflows/build-authenticator.yml +++ b/.github/workflows/build-authenticator.yml @@ -239,7 +239,12 @@ jobs: serviceCredentialsFile:"$PLAY_STORE_CREDS_FILE" \ - name: Pin release to Source Fastlane track - if: ${{ matrix.variant == 'aab' && env.PUBLISH_TO_PLAY_STORE == 'true' }} + # Gated to release/* branches only: main pushes build continuously and would + # otherwise overwrite this track just as often as "internal" does, reintroducing + # the exact staleness bug this step exists to fix. Release candidates are cut on + # dedicated release/-rcN branches, so pinning only there keeps the + # candidate stable on Source Fastlane until the next RC is cut. + if: ${{ matrix.variant == 'aab' && env.PUBLISH_TO_PLAY_STORE == 'true' && startsWith(github.ref, 'refs/heads/release/') }} env: PLAY_STORE_CREDS_FILE: ${{ github.workspace }}/secrets/authenticator_play_store-creds.json VERSION_CODE: ${{ needs.version.outputs.version_number || github.run_number }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ef50531183a..8e5fd831c92 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -335,7 +335,12 @@ jobs: bundle exec fastlane publishBetaToPlayStore - name: Pin release to Source Fastlane track - if: ${{ matrix.variant == 'prod' && matrix.artifact == 'aab' && env.PUBLISH_TO_PLAY_STORE == 'true' }} + # Gated to release/* branches only: main pushes build continuously and would + # otherwise overwrite this track just as often as "internal" does, reintroducing + # the exact staleness bug this step exists to fix. Release candidates are cut on + # dedicated release/-rcN branches, so pinning only there keeps the + # candidate stable on Source Fastlane until the next RC is cut. + if: ${{ matrix.variant == 'prod' && matrix.artifact == 'aab' && env.PUBLISH_TO_PLAY_STORE == 'true' && startsWith(github.ref, 'refs/heads/release/') }} env: PLAY_STORE_CREDS_FILE: ${{ github.workspace }}/secrets/play_creds.json VERSION_CODE: ${{ needs.version.outputs.version_number || github.run_number }} diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 312b5bab8ee..265a19afedd 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -570,12 +570,18 @@ platform :android do release_options[:version_name] = options[:versionName] end - # Only override json_key if an explicit credentials file is provided (e.g. when this - # lane is invoked for a package whose credentials aren't at the Appfile default path, - # such as com.bitwarden.authenticator). When omitted, supply() falls back to the - # Appfile default (secrets/play_creds.json), preserving existing behavior. + # Resolve json_key the same way getLivePlayStoreVersion/getLatestPlayStoreVersion do: + # honor an explicit override, otherwise map from packageName. Needed so existing callers + # that never pass serviceCredentialsFile (e.g. bitwarden/deploy's production-promotion + # workflow) still authenticate with the right key for com.bitwarden.authenticator instead + # of silently falling through to the Appfile default (secrets/play_creds.json). if options[:serviceCredentialsFile] release_options[:json_key] = options[:serviceCredentialsFile] + else + case options[:packageName] + when "com.bitwarden.authenticator" + release_options[:json_key] = "secrets/authenticator_play_store-creds.json" + end end if options[:releaseNotes].nil? or options[:releaseNotes].to_s.empty?