Skip to content

fix(capgo): give every OTA upload a unique bundle version - #2795

Merged
innolope-dev merged 1 commit into
devfrom
fix/capgo-ota-bundle-version-2
Aug 22, 2026
Merged

fix(capgo): give every OTA upload a unique bundle version#2795
innolope-dev merged 1 commit into
devfrom
fix/capgo-ota-bundle-version-2

Conversation

@innolope-dev

Copy link
Copy Markdown
Collaborator

Closes #2638. Replaces #2708 — same change, rebuilt on current dev (see Why a new PR below).

Problem

package.json's version only moves on a native release, so every OTA upload after the first reused the same bundle version. Capgo failed those uploads loudly until #2636 added --version-exists-ok, which turned them into silent no-ops: green check, nothing shipped.

The fix that must not be used — and is currently in dev

Appending the short SHA looks equivalent and is not. 1.0.53-abc1234 is a semver prerelease and sorts below plain 1.0.53. Capgo's update endpoint rejects any bundle that sorts under the device's native version (disable_auto_update_under_native, on by default — update.ts). So the upload succeeds, the dashboard shows a new bundle, and every device on the shipped binary refuses it.

capgo-deploy.yml on dev does exactly this for every non-production lane today.

Changes

  • scripts/capgo-bundle-version.mjs — prints <major>.<minor> from package.json + the git commit count (today: 1.0.10175). Anchoring major/minor to package.json keeps the version above the native floor when a native release bumps it; the commit count makes it unique per commit. It refuses a shallow clone (would emit 1.0.1), a prerelease override, and any CAPGO_BUNDLE_VERSION override below the native version.
  • capgo-deploy.ymlfetch-depth: 0, a "Resolve bundle version" step, --bundle <version> on the upload, and a post-upload assertion that channel currentBundle actually serves the version the run just built. --version-exists-ok stays, but now only covers a re-run of the same commit; any other no-op leaves the channel stale and turns the job red. That check is Capgo OTA: --version-exists-ok greens the check but ships nothing (version never bumped) #2638's "verify when fixed" criterion, enforced in CI.
  • Production is deliberately left alone. ota-1.0.48 is already plain and above the native floor, and ota-1.0.48-hotfix1 is an intentional prerelease that still sorts above it. Production keeps its tag-derived version and does not go through the script, which refuses prereleases by design.
  • capgo:upload:dev|staging package scripts get the same derived version — they had the identical collision.

Verification

  • 6 new jest tests (scripts/__tests__/capgo-bundle-version.test.js), including a real shallow-clone repo.
  • The verification step was simulated under bash -e for match / stale-channel / CLI-error; it is green only on a real match.
  • prettier --check clean on the touched files; the workflow parses.

Why a new PR

#2708 was cut on 2026-08-17. Since then dev deleted capgo-deploy-ios.yml (folded into capgo-deploy.yml), moved production onto ota-* tags, and added the SHA-suffix scheme above — so #2708 conflicted. It could not be fixed in place: merging dev into that branch drags in 7 pre-2026-08-18 unsigned src/content bumps and the "All branches" ruleset rejects the push (GH013), while non_fast_forward blocks the rebase escape hatch. Same content, rebuilt on dev, reconciled with dev's consolidated lane. #2708 is closed in favour of this.

Before the first release

Check on the Capgo dashboard that the channels' update policy is not patch — that setting blocks any patch-differing bundle, i.e. all OTA, regardless of the version scheme. Also note a native release that overrides versionName without bumping package.json would strand OTA bundles under the shipped binary.

Closes #2638

`package.json`'s version only moves on a native release, so every OTA upload
after the first reused the same bundle version. Capgo failed those uploads
loudly until #2636 added `--version-exists-ok`, which turned them into silent
no-ops: green check, nothing shipped.

The obvious fix — appending the short SHA — is the one shape that must not be
used, and is what the workflow does today for every non-production lane.
`1.0.53-abc1234` is a semver prerelease and sorts BELOW plain `1.0.53`. Capgo's
update endpoint rejects any bundle sorting under the device's native version
(`disable_auto_update_under_native`, on by default), so those uploads succeed,
appear on the dashboard, and are refused by every device on the shipped binary.

- `scripts/capgo-bundle-version.mjs` prints `<major>.<minor>` from
  `package.json` plus the git commit count (today: `1.0.10175`). Anchoring
  major/minor to `package.json` keeps the version above the native floor when a
  native release bumps it; the commit count makes it unique per commit. It
  refuses a shallow clone (would emit `1.0.1`), a prerelease override, and any
  `CAPGO_BUNDLE_VERSION` override below the native version.
- `capgo-deploy.yml` gains `fetch-depth: 0` and a "Resolve bundle version" step.
  The tag-derived production version is unchanged: `ota-1.0.48` is already plain
  and above the native floor, and `ota-1.0.48-hotfix1` is a deliberate
  prerelease that still sorts above it, so production does not go through the
  script — which refuses prereleases by design. Every other lane now derives the
  commit-count version instead of suffixing the SHA.
- A post-upload step asserts the channel actually serves the version the run
  just built. `--version-exists-ok` stays, but now only covers a re-run of the
  same commit; any other no-op leaves the channel stale and turns the job red.
- `capgo:upload:dev|staging` get the same derived version — same collision.

6 new jest tests, including a real shallow-clone repo. The verification step was
simulated under `bash -e` for match / stale-channel / CLI-error; it is green only
on a real match.

Before the first release: check on the Capgo dashboard that the channels' update
policy is not `patch` — that setting blocks any patch-differing bundle, i.e. all
OTA, regardless of the version scheme. Note also that a native release which
overrides `versionName` without bumping `package.json` would strand OTA bundles
under the shipped binary.
@innolope-dev innolope-dev self-assigned this Aug 22, 2026
@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview Aug 22, 2026 2:27pm

Request Review

@innolope-dev

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in: 29 minutes

Limit details: You’ve used all 3 included reviews currently available. Your 49 included PR review attempts over the past 7 days set your current allowance at 3 reviews per hour.

You’re in a promotional period — use the checkbox below to run this review for free:

  • Run review for free

On-demand reviews are free for the next 29 days. After that, they cost $0.25 per reviewed file.

How can I continue?

Run this review now using the option above, or comment @coderabbitai review --use-credits.

You can also wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 59d5f5d9-e29a-4315-ac5b-840017a7f846

📥 Commits

Reviewing files that changed from the base of the PR and between a187dc4 and ee70497.

📒 Files selected for processing (4)
  • .github/workflows/capgo-deploy.yml
  • package.json
  • scripts/__tests__/capgo-bundle-version.test.js
  • scripts/capgo-bundle-version.mjs

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 7322.72 → 7322.72 (0)
Findings: 0 net (+0 new, -0 resolved)

@github-actions

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 3463 ran, 0 failed, 0 skipped, 42.1s

📊 Coverage (unit)

metric %
statements 68.3%
branches 53.6%
functions 59.4%
lines 69.2%
⏱ 10 slowest test cases
time test
2.1s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.0s src/hooks/query/__tests__/user.test.tsx › does NOT clear a token that rotated mid-request (stale 401 racing a fresh login)
0.7s scripts/__tests__/capgo-bundle-version.test.js › derives .. from package.json
0.6s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.4s src/utils/__tests__/sentry.utils.test.ts › defaults to the client budget under a browser global
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.3s src/utils/__tests__/crisp.test.ts › configures once across repeated support opens
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › the lazy bank view survives a re-render without blanking
0.3s src/hooks/__tests__/useCrispTokenId.test.ts › retries then stays undefined when the endpoint keeps failing (no fallback token)
0.3s src/utils/__tests__/sentry.utils.test.ts › still lets a per-call timeoutMs win over the default
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant