chore: Release 5.4.4#1945
Conversation
There was a problem hiding this comment.
LGTM, straightforward version bump for release 5.4.4.
Extended reasoning...
Overview
This PR is a single-line version bump in package.json from 5.4.3 to 5.4.4, accompanying the release of v5.4.4. The release bundles previously-merged PRs (#1942 E2E test workflow, #1944 react-native peer dep bump).
Security risks
None. This is purely a metadata version field change with no executable code, dependencies, or configuration semantics affected.
Level of scrutiny
Minimal scrutiny is appropriate. This is a mechanical release-management change that follows the established pattern used for prior releases. The diff is exactly one line.
Other factors
No bugs were flagged by the bug hunting system. The actual feature/fix changes were already reviewed and merged in their respective PRs (#1942, #1944). No outstanding reviewer comments to address.
There was a problem hiding this comment.
Additional findings (outside current diff — PR may have been updated during review):
-
🟡
examples/setup.sh:38-51— examples/setup.sh was migrated from bun→vp commands at lines 41/48/66/67, but the surrounding comments at lines 43, 53, 60 and the user-visible echo at line 65 still name 'bun' as the active tool. The echo prints 'Registering tarball with bun (refreshes bun.lock integrity hash)...' on every setup run — actively misleading anyone debugging a CI log who greps for 'bun' expecting to find a bun cache to inspect. Nit, doc-only — vp wraps bun under the hood (packageManager: bun@1.3.13) so functional behavior is unchanged, but the literal command names cited in the comments (bun pm pack,bun add,bun remove) no longer exist as call sites in the file.Extended reasoning...
What is stale
Verified directly from the diff and the current file. The PR converts four commands from bun to vp:
- Line 41:
vp run build(wasbun run build) - Line 48:
vp pm pack(wasbun pm pack) - Line 66:
vp remove react-native-onesignal(wasbun remove ...) - Line 67:
vp add file:../../react-native-onesignal.tgz(wasbun add ...)
…but the surrounding documentation block still names bun by its CLI:
- Line 43:
#bun pm packhonors package.json's "files" field …— describes the command on line 48, which is nowvp pm pack. - Line 53:
# Always go through bun add so bun.lock's integrity hash …— describes line 67, which is nowvp add. - Line 57:
# subsequentbun installthat re-resolved this entry … - Line 60:
#bun removefirst because bun verifies the existing integrity hash— describes line 66, which is nowvp remove. - Line 65:
echo "Registering tarball with bun (refreshes bun.lock integrity hash)..."— printed to stdout on every run.
The refutation is partially right
A verifier correctly noted that vp wraps the underlying package manager (per AGENTS.md), and
examples/demo/package.jsondeclarespackageManager: bun@1.3.13. So the integrity-hash mechanics andbun.lockfilename references in the comments remain technically accurate — bun is still the package manager doing the work, just behind the vp wrapper. That's why this is a nit, not a real bug.Why it's still worth flagging
-
The echo is genuinely misleading in a CI context. A future operator reading
Registering tarball with bun (refreshes bun.lock integrity hash)…in a workflow log will grep the script forbunto debug a stale-tarball or cache issue, and find only this echo — no actualbuninvocation. The command they need to inspect isvp, notbun. -
The literal command names cited in the comments don't appear at the call site. A reader applying the rule 'comments should match the code they document' would update line 43 to mention
vp pm pack(or both names), line 53 to mentionvp add, and line 60 to mentionvp remove. -
Header comment at line 3 (
Invoked from a demo dir … viabun run setup``) is not stale —examples/demo/package.jsonlines 7-8 still use `bun run setup` for `preandroid`/`preios`, so that reference is correct.
Step-by-step proof
- Open
examples/setup.shat this PR's HEAD. - Read line 65:
echo "Registering tarball with bun (refreshes bun.lock integrity hash)...". This string is unconditionally written to stdout when setup runs. - Read lines 66-67:
vp remove react-native-onesignal/vp add file:../../react-native-onesignal.tgz. The actual commands invoked arevp, notbun. - Run
grep -n bun examples/setup.shmentally: matches at lines 3, 43, 53, 56, 57, 60, 62, 65 — all comments or the echo, no realbuninvocations remain in the script body.
Suggested fix
Either (a) rephrase to name the wrapper and the underlying tool together, e.g.
echo "Registering tarball with vp (which delegates to bun and refreshes bun.lock's integrity hash)...", or (b) just s/bun/vp/g in the comments and echo, since vp is now the only literal command in the file. Option (a) preserves the explanatory value of mentioning bun.lock; option (b) is the smaller diff.Severity
nit — functional behavior is unchanged, the bun.lock filename references remain accurate, and the fix is purely cosmetic. Flagging because the misleading echo will appear in every CI log going forward and the comments were touched-by-proximity in this exact diff hunk.
- Line 41:
| ( | ||
| cd examples/demo | ||
| vp run setup | ||
| cd ios | ||
| pod update OneSignalXCFramework --no-repo-update | ||
| ) |
There was a problem hiding this comment.
🟡 The new pod update block in the "Update iOS SDK version" step runs vp run setup and pod update OneSignalXCFramework BEFORE the "Update sdk version" step bumps package.json. Since react-native-onesignal.podspec derives s.version from package.json and CocoaPods always re-evaluates path-based pods, the react-native-onesignal entry in Podfile.lock will be written with the OLD version on every future release — perpetuating the same lock-drift this automation was added to fix. Worse: for RN-only releases (no ios_version) the entire iOS step is skipped, so Podfile.lock is never regenerated at all. Fix: reorder so "Update sdk version" runs first, or add a follow-up vp run setup && pod install after the version bump.
Extended reasoning...
What is wrong
The new "Update iOS SDK version" step (.github/workflows/create-release-pr.yml:126-159) was added by this PR with the goal of keeping examples/demo/ios/Podfile.lock in sync with podspec bumps. It does:
sed -i "s/.../OneSignalXCFramework, $VERSION/" react-native-onesignal.podspec
( cd examples/demo
vp run setup
cd ios
pod update OneSignalXCFramework --no-repo-update )But this step runs before the next step (Update sdk version, lines 161-194) which is the one that does npm pkg set version="$NEW_VERSION". At the moment vp run setup and pod update execute, package.json still holds the previous SDK version.
The podspec derives its own version from package.json:
# react-native-onesignal.podspec
package_json = JSON.parse(File.read(package.json))
Pod::Spec.new do |s|
s.version = package_json["version"]CocoaPods always re-evaluates path-based pods on every pod install/pod update (the local source is mutable, so it has no version locking and re-reads the podspec each time). So the lock entry written for react-native-onesignal reflects the OLD package.json version, not the version this release is producing. Step 2 then bumps package.json but does no pod operations, so Podfile.lock is never refreshed against the new version.
Why nothing catches it
git diff --staged --quiet && exit 0in theUpdate sdk versionstep only checks for staged changes, not for self-consistency. The earliernpm pkg set versionalways mutatespackage.json, so the guard always falls through to commit/push.- The
Update iOS SDK versionstep is conditional oninputs.ios_version !=. For an RN-only release (no iOS bump), it is skipped entirely —Podfile.lockis never touched, so thereact-native-onesignalentry stays at whatever the previous iOS-bump release wrote. Drift compounds. - This was masked in this PR because the workflow had previously been run in stages (an earlier run produced commit
1404a9a"Release 5.4.4" before the iOS automation existed), so by the time the iOS step ran,package.jsonwas already at 5.4.4. A normal future release that runs all three steps in one workflow execution will hit the bug.
Step-by-step proof for the next 5.4.4 → 5.5.0 release (with an iOS bump)
- Step "Update iOS SDK version" begins. SDK
package.jsonsays5.4.4. sedupdates podspec OneSignalXCFramework pin →5.5.x.vp run setuprebuilds the tarball fromSDK_ROOT/package.json(still5.4.4) andbun adds it intoexamples/demo/node_modules/react-native-onesignal/— that node_modulespackage.jsonis5.4.4.pod update OneSignalXCFramework --no-repo-updateruns. CocoaPods updatesOneSignalXCFrameworkto the new version, then re-evaluates the path-basedreact-native-onesignalpod by readingnode_modules/react-native-onesignal/package.json→ version5.4.4.Podfile.locklinereact-native-onesignal (5.4.4)is written.- Step "Update sdk version":
npm pkg set version="5.5.0"→ SDKpackage.jsonnow5.5.0. Wrapper literals updated. No pod operations. - Commit & push.
After the workflow completes, the committed Podfile.lock says react-native-onesignal (5.4.4) while package.json says 5.5.0. The lock is exactly one release behind on the wrapper itself, every release. The OneSignalXCFramework pin (the visible change reviewers focus on) is correct because that is what pod update was told to refresh.
Impact
CI is unaffected: setup-demo runs vp run update:pods before xcodebuild, so the runner regenerates the lock in-place against the freshly-pinned podspec. End users install via npm, so examples/demo is internal. The real impact is that the released tag is self-inconsistent — a developer who clones rel/X.Y.Z and runs pod install directly will see CocoaPods detect the mismatch (podspec exact-pin vs. lock) and either error or auto-rewrite the lock on a fresh checkout, dirtying the working tree. This is precisely the failure mode this automation was added to prevent.
How to fix
Either:
- Reorder the job so "Update sdk version" runs before "Update iOS SDK version". With
package.jsonalready at the new version,vp run setuppacks the new tarball and the path-based lock entry gets the correct version. The OneSignalXCFramework pin update still happens as part of the iOS step. - Or, after
npm pkg set versionin the "Update sdk version" step, add a follow-up(cd examples/demo && vp run setup && cd ios && pod install --no-repo-update)so the lock catches up to the new wrapper version regardless of whether iOS was bumped.
Severity
nit — same impact class as the existing release-hygiene observations: cosmetic for CI/end-users, but the committed lockfile in every future release tag is one wrapper version behind, contradicting the stated purpose of this new automation block.
| "android": "bash ../run-android.sh", | ||
| "ios": "bash ../run-ios.sh", | ||
| "update:pods": "cd ios && pod update OneSignalXCFramework --no-repo-update && cd ..", | ||
| "update:pods": "(cd ios && pod update OneSignalXCFramework --no-repo-update)", |
There was a problem hiding this comment.
🟡 Nit: the bun → vp migration is incomplete in two spots that should be aligned while the rest is fresh. (1) examples/demo/package.json:7-8 still hardcodes preandroid and preios to bun run setup — every other entry point (root scripts, setup-demo action, examples/setup.sh) now uses vp, and the setup-demo action dropped its oven-sh/setup-bun step, so a developer who runs vp run android after this PR could trigger the pre-hook on a runner where bun is no longer guaranteed installed. (2) examples/setup.sh:44,54-66 still has comments and a user-facing echo ("Registering tarball with bun (refreshes bun.lock integrity hash)…") that name bun, while lines 42/49/67/68 are now vp run build/vp pm pack/vp remove/vp add. CI is unaffected (it bypasses the demo pre-hooks and bun is still pinned via packageManager: bun@1.3.13 so vp likely delegates), but both are one-line consistency fixes worth folding into this PR.
Extended reasoning...
What is wrong
Two leftover bun references survived this PR's broader bun → vp migration:
1. examples/demo/package.json:7-8 — pre-hooks still call bun
"preandroid": "bun run setup",
"preios": "bun run setup",Everything else in the migration moved to vp:
.github/actions/setup-demo/action.ymldroppedSet up Bun(oven-sh/setup-bun@v2) and now installs only Vite+ viavoidzero-dev/setup-vp@v1. The composite invokesvp run setupandvp installdirectly.examples/setup.shswitched all four invocations tovp run build/vp pm pack/vp remove/vp add.- Root
package.jsonscripts usevp.
npm-style pre-/post- script hooks are honored by Vite+ (and bun/npm/yarn) the same way, so vp run android dispatches preandroid first — which then explicitly invokes bun.
2. examples/setup.sh — comments and echo still describe bun
- Line 44:
#bun pm packhonors package.json's "files" field…(the actual command on line 49 is nowvp pm pack) - Lines 54-59:
# Always go through bun add so bun.lock's integrity hash…and# subsequentbun install… would fail with IntegrityCheckFailed - Lines 61-63:
#bun removefirst because bun verifies the existing integrity hash… - Line 66:
echo "Registering tarball with bun (refreshes bun.lock integrity hash)…"— the user sees this in CI logs but the next two lines runvp removeandvp add.
Impact
Both are nit-severity. CI is unaffected for two independent reasons: the workflow calls the setup-demo composite directly (which runs vp run setup, never the demo pre-hooks), and packageManager: bun@1.3.13 is still pinned in the root package.json so vp likely delegates to bun under the hood — meaning the integrity-hash rationale in the setup.sh comments is still substantively correct, just no longer surface-consistent with the visible commands.
The real cost is future-maintainer confusion: someone reading setup.sh sees vp commands documented with bun rationale and reasonably asks whether IntegrityCheckFailed still applies; someone running vp run android on a fresh checkout that doesn't have bun installed gets a hard failure from preandroid.
How to fix
- "preandroid": "bun run setup",
- "preios": "bun run setup",
+ "preandroid": "vp run setup",
+ "preios": "vp run setup",In setup.sh, either generalize the language ("the package manager refreshes…") or note that vp delegates to bun (per packageManager: bun@1.3.13) so the bun-specific failure-mode rationale still applies.
Step-by-step proof — preandroid hook
- Developer follows this PR's direction and installs Vite+ locally (no bun).
- Runs
vp run androidinexamples/demo. - Vite+ honors npm-style pre-hooks → dispatches
preandroid. preandroidisbun run setup.bunis not on PATH.- Hook exits non-zero; the android build never starts.
Step-by-step proof — setup.sh log/comment drift
- CI runs
bash examples/setup.shvia the setup-demo composite. - Log line 1 (in CI output):
Registering tarball with bun (refreshes bun.lock integrity hash)… - Next two commands actually executed:
vp remove react-native-onesignalandvp add file:../../react-native-onesignal.tgz. - The CI log line and the source comments contradict the commands a reader sees on the very next line. Severity: nit, future-maintainer confusion only.
There was a problem hiding this comment.
Thanks — examples/demo/package.json:7-8 is fixed. The second half of the comment (bun references inside examples/setup.sh) is still pending though: line 44 (# bun pm pack honors…), the comment block at 54-65 (bun add / bun remove / IntegrityCheckFailed rationale), and the user-facing echo at line 66 (Registering tarball with bun…) all still name bun even though the commands directly underneath are now vp pm pack / vp remove / vp add. Still a nit, but worth folding in while this PR is open since the rest of the migration is fresh.
| - name: Resolve OneSignal Android SDK version | ||
| id: android-sdk-version | ||
| run: | | ||
| VERSION=$(grep "com.onesignal:OneSignal:" android/build.gradle | sed -E "s/.*OneSignal:([0-9.]+).*/\1/") | ||
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" |
There was a problem hiding this comment.
🟡 Forward-looking nit at .github/workflows/e2e.yml:52 (mirrored at create-release-pr.yml:95): the sed -E "s/.*OneSignal:([0-9.]+).*/\1/" capture group only matches digits and dots, so a future Android SDK pin like 5.8.0-rc.1 or 5.9.0-beta.2 would be truncated to 5.8.0 / 5.9.0. The wait-for-maven-artifact step would then either succeed immediately on a stale prior artifact (defeating the wait) or fail outright. Current pin (5.8.0) is clean semver so no immediate impact — fix is one character: broaden the class to capture up to the closing quote, e.g. ([^"']+).
Extended reasoning...
What is wrong
The new "Resolve OneSignal Android SDK version" step at .github/workflows/e2e.yml:49-53 extracts the pinned Android SDK version with:
VERSION=$(grep "com.onesignal:OneSignal:" android/build.gradle | sed -E "s/.*OneSignal:([0-9.]+).*/\1/")The character class [0-9.]+ matches only digits and dots. If the dep is ever pinned to a pre-release or build-metadata version such as com.onesignal:OneSignal:5.8.0-rc.1 or 5.9.0-beta.2 (the OneSignal Android SDK does ship -rc/-beta tags historically), the captured VERSION truncates at the first hyphen — yielding 5.8.0 or 5.9.0.
Code path that triggers it
The truncated VERSION is then handed to the next step (wait-for-maven-artifact@main) via ${{ steps.android-sdk-version.outputs.version }}. Two failure modes:
- Lenient action — finds the stale prior
5.8.0artifact already on Maven Central, returns success immediately. The subsequent./gradlew assembleReleasethen races the actual freshly-published5.8.0-rc.1artifact, exactly the failure the wait was added to prevent. - Strict action — looks for
5.8.0(which does not match the actual5.8.0-rc.1Maven coordinate), fails outright with "version not found".
Why nothing prevents it
This PR pins the Android SDK to a clean 5.8.0, so the regex matches losslessly today. The defect is forward-looking: the same regex runs on every future rel/** push, and every future Android SDK bump — including any -rc/-beta cut — flows through it. There is no validation step that compares the captured VERSION against the actual gradle line.
The same [0-9.]+ pattern is mirrored in .github/workflows/create-release-pr.yml:95 (the ANDROID_VERSION extraction in get-current-versions) and at line 96 (the iOS version regex inside single quotes), so the limitation is consistent across the workflow surface. The wait-for-maven-artifact path is uniquely exposed because a wrong version makes the wait silently inert.
Impact
- Today: zero —
5.8.0matches losslessly. - Next
-rc/-betabump: either e2e fails on a non-existent Maven coordinate, or (worse) the wait silently no-ops andassembleReleasere-introduces the cold-CDN race the wait step was added to prevent. Either way, the protection this PR is adding is incomplete for the very class of bumps where artifact-propagation latency matters most (release-candidate cuts).
Step-by-step proof
- Suppose a future PR pins
api('com.onesignal:OneSignal:5.8.0-rc.1')inandroid/build.gradle. - The "Resolve OneSignal Android SDK version" step runs:
grep "com.onesignal:OneSignal:" android/build.gradle→api('com.onesignal:OneSignal:5.8.0-rc.1') {. - Pipe into
sed -E "s/.*OneSignal:([0-9.]+).*/\1/". The greedy[0-9.]+matches5.8.0and stops at the hyphen (-is not in the character class). VERSION=5.8.0is written to$GITHUB_OUTPUT.wait-for-maven-artifactis invoked withversion: 5.8.0. The actual newly-published artifact is5.8.0-rc.1. The wait either matches the previously-published5.8.0(returns immediately, defeating the wait) or fails to find5.8.0and errors../gradlew assembleReleaseruns against the real5.8.0-rc.1coordinate frombuild.gradle, racing CDN propagation — the original failure mode.
How to fix
One-character broaden — capture everything up to the closing quote:
VERSION=$(grep "com.onesignal:OneSignal:" android/build.gradle | sed -E "s/.*OneSignal:([^\"'\)[:space:]]+).*/\1/")or more simply ([^'\")]+). Either preserves -rc.1 / -beta.2 / +build.5 suffixes losslessly. The mirrored regex at create-release-pr.yml:95 would benefit from the same one-char change for consistency.
Severity
nit — current pin is clean semver, so no immediate impact. Worth flagging because the same wait-step pattern will run on every future Android SDK bump, and the consequence (silently inert wait) defeats the very protection this PR is adding.
| # Update demo app's Podfile.lock with the new iOS SDK version. | ||
| # setup.sh repacks the SDK tarball (so node_modules sees the new pin), | ||
| # then pod update rewrites the OneSignalXCFramework lock entry only. | ||
| # --no-repo-update is fine because trunk is a CDN and serves the new | ||
| # spec on demand without a local refresh. | ||
| ( | ||
| cd examples/demo | ||
| vp run setup | ||
| cd ios | ||
| pod update OneSignalXCFramework --no-repo-update | ||
| ) | ||
| echo "✓ Updated examples/demo/ios/Podfile.lock to OneSignalXCFramework ${VERSION}" |
There was a problem hiding this comment.
🟣 The release-tag existence guards at lines 107-115 (Android) and 131-138 (iOS) use curl -s and check [ -z "$RELEASE" ], but curl -s only silences the progress meter — on a 404 GitHub's API still writes a JSON error body ({"message":"Not Found",...}, ~147 bytes) to stdout and exits 0, so RELEASE is never empty and the guard never fires for the typo case it exists to catch. This PR's new pod-update block at lines 143-154 sits immediately after the broken iOS guard, so a typo in inputs.ios_version flows past validation, sed rewrites the podspec, and pod update OneSignalXCFramework fails opaquely; the Android branch is worse — no downstream pod-update backstop, so a typo lands on rel/X.Y.Z via git commit && git push. Fix: curl -sf (--fail) so curl exits non-zero on HTTP 4xx/5xx and set -e aborts, or pipe the response through jq -e .id. Pre-existing, but this PR extends the surrounding step and is the natural place to harden the guard.
Extended reasoning...
What is wrong
The "release exists" guards on the Android (lines 107-115) and iOS (lines 131-138) branches both look like:
RELEASE=$(curl -s -H "Authorization: token ${{ github.token }}" \
"https://api.github.com/repos/OneSignal/OneSignal-iOS-SDK/releases/tags/${VERSION}")
if [ -z "$RELEASE" ]; then
echo "✗ iOS SDK version ${VERSION} not found"
exit 1
ficurl -s is silent mode — it suppresses the progress meter and curl's own error messages, but it does not suppress the HTTP response body and it does not change the exit code on HTTP errors. To make curl exit non-zero on 4xx/5xx you need -f/--fail (or --fail-with-body). The GitHub REST API responds to /releases/tags/<missing> with a JSON error body — {"message":"Not Found","documentation_url":"...","status":"404"} (~147 bytes) — so on a typo RELEASE is populated with that body and [ -z "$RELEASE" ] is false. The guard only fires on transport-level failures (DNS/connection error), which set -e (GitHub Actions runs run: blocks under bash --noprofile --norc -eo pipefail) would already kill earlier without help from this guard.
Why this PR is the natural place to harden it
The validation lines themselves predate this PR. But this PR adds new code immediately after the iOS guard at lines 143-154:
(
cd examples/demo
vp run setup
cd ios
pod update OneSignalXCFramework --no-repo-update
)so the new code is precisely the downstream victim of the broken guard, and the surrounding "Update iOS SDK version" step is the one being modified. The Android branch is worse: there is no downstream artifact-resolution step, so a typo just sails through sed (which succeeds on any string), gets committed, and pushed.
Step-by-step proof (Android branch — most exposed)
- Operator triggers the workflow with
android_version: 5.8.x(typo). curl -s -H "Authorization: token …" https://api.github.com/repos/OneSignal/OneSignal-Android-SDK/releases/tags/5.8.xruns. The endpoint returns HTTP 404 with body{"message":"Not Found","documentation_url":"https://docs.github.com/rest/releases/releases#get-a-release-by-tag-name","status":"404"}. curl exits 0, the body is captured intoRELEASE.[ -z "$RELEASE" ]is false (147-byte body). Guard does not fire.echo "✗ Android SDK version 5.8.x not found"is never printed.sed -i '' -E "s/(com\.onesignal:OneSignal:)[0-9.]+/\15.8.x/" android/build.gradlesucceeds (sed has no semantic validation; it just rewrites the literal).git add -A; git diff --staged --quiet && exit 0; git commit -m "Update Android SDK to 5.8.x" && git pushcommits and pushes a bogusbuild.gradleline to the release branch. e2e then fails on the next push torel/**with a Gradle "Could not find" error — but the bad commit is already on the release branch.
Step-by-step proof (iOS branch — partially caught by this PR's new code)
- Operator types
ios_version: 5.5.x(typo). curl -s ... /releases/tags/5.5.xreturns the same JSON 404 body, exits 0. Guard does not fire.sed -i '' "s/.../OneSignalXCFramework, '5.5.x'/"rewrites the podspec.- The new block at lines 143-154 runs
vp run setupand thenpod update OneSignalXCFramework --no-repo-update. CocoaPods fails withUnable to find a specification for OneSignalXCFramework (= 5.5.x). Underset -ethis aborts the step before the commit. Good — but the failure is opaque and the operator has to debug why their typo did not get caught upstream.
How to fix
Trivial — add -f (--fail) so curl exits non-zero on HTTP 4xx/5xx and set -e aborts:
RELEASE=$(curl -sf -H "Authorization: token ${{ github.token }}" \
"https://api.github.com/repos/OneSignal/OneSignal-iOS-SDK/releases/tags/${VERSION}") \
|| { echo "✗ iOS SDK version ${VERSION} not found"; exit 1; }Or parse a required field:
echo "$RELEASE" | jq -e '.id' >/dev/null || { echo "✗ iOS SDK version ${VERSION} not found"; exit 1; }Same one-character fix applies to the Android branch.
Severity
pre_existing — the broken -z guard predates this PR (the validation lines are not in the diff), but this PR extends the iOS step with a new pod-update block that exposes the failure mode more visibly, and the surrounding "Update iOS SDK version" / "Update Android SDK version" steps are the natural place to harden the guard. The Android branch in particular is unprotected by any downstream artifact-resolution step, so a typo in android_version would land on rel/X.Y.Z before anything downstream notices.
Channels: Current
🐛 Bug Fixes
🛠️ Native Dependency Updates