Skip to content

Commit 97883f3

Browse files
committed
Fix version sync race condition between App and Mobile-Expensify
This addresses the issue where cherry-pick workflows fail because Mobile-Expensify version gets bumped but E/App does not, leaving the repos out of sync. Changes: - createNewVersion.yml: Replace single retry with 5-attempt retry loop with exponential backoff for E/App push - createNewVersion.yml: Add version verification step after pushes - cherryPick.yml: Fail fast with exit 1 when version bump commits can't be found, with helpful error messages - syncVersions.yml: New recovery workflow for manual intervention when versions get out of sync (restricted to mobile deployers)
1 parent a5ec775 commit 97883f3

3 files changed

Lines changed: 206 additions & 18 deletions

File tree

.github/workflows/cherryPick.yml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,12 @@ jobs:
116116
VERSION_BUMP_COMMIT="$(git log -1 --format='%H' --author='OSBotify' --grep 'Update version to ${{ needs.createNewVersion.outputs.NEW_VERSION }}')"
117117
if [ -z "$VERSION_BUMP_COMMIT" ]; then
118118
echo "::error::❌ Could not find E/App version bump commit for ${{ needs.createNewVersion.outputs.NEW_VERSION }}"
119-
git log --oneline
120-
else
121-
echo "::notice::👀 Found E/App version bump commit $VERSION_BUMP_COMMIT"
119+
echo "::error::This may indicate E/App and Mobile-Expensify versions are out of sync"
120+
echo "::error::Run the syncVersions workflow to fix, then retry this cherry-pick"
121+
git log --oneline -20
122+
exit 1
122123
fi
124+
echo "::notice::👀 Found E/App version bump commit $VERSION_BUMP_COMMIT"
123125
echo "VERSION_BUMP_SHA=$VERSION_BUMP_COMMIT" >> "$GITHUB_OUTPUT"
124126
125127
- name: Get Mobile-Expensify version bump commit
@@ -130,10 +132,12 @@ jobs:
130132
VERSION_BUMP_COMMIT="$(git log -1 --format='%H' --author='OSBotify' --grep 'Update version to ${{ needs.createNewVersion.outputs.NEW_VERSION }}')"
131133
if [ -z "$VERSION_BUMP_COMMIT" ]; then
132134
echo "::error::❌ Could not find Mobile-Expensify version bump commit for ${{ needs.createNewVersion.outputs.NEW_VERSION }}"
133-
git log --oneline
134-
else
135-
echo "::notice::👀 Found Mobile-Expensify version bump commit $VERSION_BUMP_COMMIT"
135+
echo "::error::This may indicate E/App and Mobile-Expensify versions are out of sync"
136+
echo "::error::Run the syncVersions workflow to fix, then retry this cherry-pick"
137+
git log --oneline -20
138+
exit 1
136139
fi
140+
echo "::notice::👀 Found Mobile-Expensify version bump commit $VERSION_BUMP_COMMIT"
137141
echo "VERSION_BUMP_SHA=$VERSION_BUMP_COMMIT" >> "$GITHUB_OUTPUT"
138142
139143
- name: Get merge commit for pull request to CP

.github/workflows/createNewVersion.yml

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -127,23 +127,50 @@ jobs:
127127
run: |
128128
git add Mobile-Expensify
129129
git commit -m "Update Mobile-Expensify submodule version to ${{ steps.bumpVersion.outputs.NEW_VERSION }}"
130-
if ! git push origin main; then
131-
echo "Race condition! E/App main was updated while this workflow was running, so push failed. Fetching remote, rebasing, and retrying push once."
130+
131+
MAX_RETRIES=5
132+
RETRY_DELAY=2
133+
134+
for i in $(seq 1 $MAX_RETRIES); do
135+
if git push origin main; then
136+
echo "::notice::Successfully pushed E/App changes on attempt $i"
137+
exit 0
138+
fi
139+
140+
echo "::warning::Push attempt $i failed, retrying in ${RETRY_DELAY}s..."
141+
sleep $RETRY_DELAY
142+
RETRY_DELAY=$((RETRY_DELAY * 2))
143+
132144
if ! git fetch origin main; then
133-
echo "::error:: ❌ Unable to fetch main"
134-
echo "::error:: This can happen when Mobile-Expensify and E/App repos got out of sync."
135-
echo "::error:: We likely need to manually bump the version in E/App to match Mobile-Expensify."
136-
exit 1
145+
echo "::error::Unable to fetch main on attempt $i"
146+
continue
137147
fi
148+
138149
if ! git rebase origin/main; then
139-
echo "::error:: Rebase failed while retrying E/App push"
140-
exit 1
141-
fi
142-
if ! git push origin main; then
143-
echo "::error:: E/App change failed to push after rebase"
144-
exit 1
150+
echo "::warning::Rebase failed on attempt $i, aborting and retrying..."
151+
git rebase --abort || true
152+
continue
145153
fi
154+
done
155+
156+
echo "::error::E/App push failed after $MAX_RETRIES attempts"
157+
echo "::error::CRITICAL: Mobile-Expensify has version ${{ steps.bumpVersion.outputs.NEW_VERSION }} but E/App does not!"
158+
echo "::error::Manual intervention required - run the syncVersions workflow to fix"
159+
exit 1
160+
161+
- name: Verify version sync
162+
run: |
163+
APP_VERSION=$(jq -r .version package.json)
164+
ME_VERSION=$(jq -r .meta.version Mobile-Expensify/app/config/config.json)
165+
166+
if [ "$APP_VERSION" != "$ME_VERSION" ]; then
167+
echo "::error::Version mismatch detected after push!"
168+
echo "::error::E/App version: $APP_VERSION"
169+
echo "::error::Mobile-Expensify version: $ME_VERSION"
170+
echo "::error::Run the syncVersions workflow to fix this"
171+
exit 1
146172
fi
173+
echo "::notice::Versions verified: $APP_VERSION"
147174
148175
- name: Announce failed workflow in Slack
149176
if: ${{ failure() }}

.github/workflows/syncVersions.yml

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
name: Sync E/App and Mobile-Expensify versions
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
TARGET_VERSION:
7+
description: 'Version to sync to (leave empty to auto-detect from Mobile-Expensify)'
8+
required: false
9+
type: string
10+
11+
jobs:
12+
syncVersions:
13+
runs-on: macos-latest
14+
steps:
15+
- name: Check out
16+
# v4
17+
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
18+
with:
19+
ref: main
20+
submodules: true
21+
# The OS_BOTIFY_COMMIT_TOKEN is a personal access token tied to osbotify
22+
# This is a workaround to allow pushes to a protected branch
23+
token: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }}
24+
25+
- name: Validate actor is a mobile deployer
26+
uses: ./.github/actions/composite/validateActor
27+
with:
28+
REQUIRE_APP_DEPLOYER: true
29+
OS_BOTIFY_TOKEN: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }}
30+
31+
- name: Setup git for OSBotify
32+
uses: Expensify/GitHub-Actions/setupGitForOSBotify@main
33+
id: setupGitForOSBotify
34+
with:
35+
OP_VAULT: ${{ vars.OP_VAULT }}
36+
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
37+
SETUP_AS_APP: false
38+
39+
- name: Check current versions
40+
id: checkVersions
41+
run: |
42+
APP_VERSION=$(jq -r .version package.json)
43+
ME_VERSION=$(jq -r .meta.version Mobile-Expensify/app/config/config.json)
44+
echo "E/App version: $APP_VERSION"
45+
echo "Mobile-Expensify version: $ME_VERSION"
46+
echo "APP_VERSION=$APP_VERSION" >> "$GITHUB_OUTPUT"
47+
echo "ME_VERSION=$ME_VERSION" >> "$GITHUB_OUTPUT"
48+
49+
if [ "$APP_VERSION" == "$ME_VERSION" ]; then
50+
echo "::notice::✅ Versions are already in sync: $APP_VERSION"
51+
echo "IN_SYNC=true" >> "$GITHUB_OUTPUT"
52+
else
53+
echo "::warning::⚠️ Versions are out of sync - E/App: $APP_VERSION, Mobile-Expensify: $ME_VERSION"
54+
echo "IN_SYNC=false" >> "$GITHUB_OUTPUT"
55+
fi
56+
57+
- name: Determine target version
58+
if: steps.checkVersions.outputs.IN_SYNC != 'true'
59+
id: targetVersion
60+
run: |
61+
if [ -n "${{ inputs.TARGET_VERSION }}" ]; then
62+
echo "Using provided target version: ${{ inputs.TARGET_VERSION }}"
63+
echo "VERSION=${{ inputs.TARGET_VERSION }}" >> "$GITHUB_OUTPUT"
64+
else
65+
# Use Mobile-Expensify version as source of truth since it was pushed first
66+
echo "Using Mobile-Expensify version as target: ${{ steps.checkVersions.outputs.ME_VERSION }}"
67+
echo "VERSION=${{ steps.checkVersions.outputs.ME_VERSION }}" >> "$GITHUB_OUTPUT"
68+
fi
69+
70+
- name: Setup Node
71+
if: steps.checkVersions.outputs.IN_SYNC != 'true'
72+
uses: ./.github/actions/composite/setupNode
73+
74+
- name: Sync E/App to target version
75+
if: steps.checkVersions.outputs.IN_SYNC != 'true'
76+
run: |
77+
TARGET="${{ steps.targetVersion.outputs.VERSION }}"
78+
echo "::notice::Syncing E/App to version $TARGET"
79+
80+
# Update version using npm (this updates package.json and package-lock.json)
81+
npm --no-git-tag-version version "$TARGET"
82+
83+
# Extract version components for native file updates
84+
SHORT_VERSION="${TARGET%-*}" # e.g., "9.3.11" from "9.3.11-48"
85+
BUILD_NUMBER="${TARGET#*-}" # e.g., "48" from "9.3.11-48"
86+
CF_VERSION="${SHORT_VERSION}.${BUILD_NUMBER}" # e.g., "9.3.11.48"
87+
88+
# Pad build number components for Android version code (prefix 10 for E/App)
89+
IFS='.' read -r MAJOR MINOR PATCH <<< "$SHORT_VERSION"
90+
ANDROID_VERSION_CODE="10$(printf '%02d' $MAJOR)$(printf '%02d' $MINOR)$(printf '%02d' $PATCH)$(printf '%02d' $BUILD_NUMBER)"
91+
92+
echo "Updating Android build.gradle with versionName=$TARGET, versionCode=$ANDROID_VERSION_CODE"
93+
sed -i '' "s/versionName \"[0-9.-]*\"/versionName \"$TARGET\"/" android/app/build.gradle
94+
sed -i '' "s/versionCode [0-9]*/versionCode $ANDROID_VERSION_CODE/" android/app/build.gradle
95+
96+
echo "Updating iOS plists with CFBundleShortVersionString=$SHORT_VERSION, CFBundleVersion=$CF_VERSION"
97+
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $SHORT_VERSION" ios/NewExpensify/Info.plist
98+
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $CF_VERSION" ios/NewExpensify/Info.plist
99+
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $SHORT_VERSION" ios/NotificationServiceExtension/Info.plist
100+
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $CF_VERSION" ios/NotificationServiceExtension/Info.plist
101+
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $SHORT_VERSION" ios/ShareViewController/Info.plist
102+
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $CF_VERSION" ios/ShareViewController/Info.plist
103+
104+
# Commit version changes
105+
git add package.json package-lock.json android/app/build.gradle ios/*/Info.plist
106+
git commit -m "Update version to $TARGET (sync recovery)"
107+
108+
# Update submodule reference
109+
git add Mobile-Expensify
110+
git commit -m "Update Mobile-Expensify submodule version to $TARGET (sync recovery)"
111+
112+
# Push changes
113+
if ! git push origin main; then
114+
echo "::warning::Push failed, attempting rebase..."
115+
git fetch origin main
116+
git rebase origin/main
117+
git push origin main
118+
fi
119+
120+
echo "::notice::✅ Successfully synced E/App to version $TARGET"
121+
122+
- name: Verify sync completed
123+
if: steps.checkVersions.outputs.IN_SYNC != 'true'
124+
run: |
125+
APP_VERSION=$(jq -r .version package.json)
126+
ME_VERSION=$(jq -r .meta.version Mobile-Expensify/app/config/config.json)
127+
128+
if [ "$APP_VERSION" != "$ME_VERSION" ]; then
129+
echo "::error::Sync failed! Versions still don't match"
130+
echo "::error::E/App: $APP_VERSION, Mobile-Expensify: $ME_VERSION"
131+
exit 1
132+
fi
133+
echo "::notice::✅ Versions verified: $APP_VERSION"
134+
135+
- name: Announce sync in Slack
136+
if: steps.checkVersions.outputs.IN_SYNC != 'true'
137+
# v3
138+
uses: 8398a7/action-slack@1750b5085f3ec60384090fb7c52965ef822e869e
139+
with:
140+
status: custom
141+
custom_payload: |
142+
{
143+
channel: '#deployer',
144+
attachments: [{
145+
color: "good",
146+
text: `✅ Version sync completed. E/App and Mobile-Expensify are now both at version ${{ steps.targetVersion.outputs.VERSION }}`
147+
}]
148+
}
149+
env:
150+
GITHUB_TOKEN: ${{ github.token }}
151+
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
152+
153+
- name: Announce failed workflow in Slack
154+
if: ${{ failure() }}
155+
uses: ./.github/actions/composite/announceFailedWorkflowInSlack
156+
with:
157+
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

0 commit comments

Comments
 (0)