rn-upgrade — upgrade an Expo + RN project to a newer SDK
Contract
See references/contracts.md (vendored from dev-flow). Key facts:
- Reads
<project-root>/.workflow/meta.json#stack.framework— must be"expo-rn". - Requires
meta.json#phase ≥ "scaffolded"(there must be a real app to upgrade — this skill never scaffolds). - This is a maintenance operation, not a pipeline step: it does not advance
phase. It only appends ahistoryentry recording the SDK versions before/after. - Idempotent in spirit: re-running on an already-upgraded project is a no-op after Step 1 detects
expois already at the target version (still worth runningexpo-doctorto catch drift).
When this skill applies
- Phase is
scaffoldedor later (an Expo app already exists at the project root). - User asks to move to a newer Expo SDK / React Native version, or reports
npx expo-doctorfailures, or dependency drift after manualnpm install. - Orchestrator note:
dev-flowdoes not route here automatically as part of the phase pipeline — this is invoked ad hoc, whenever the user wants to upgrade an already-running project.
Knowledge dependencies (read these first)
rn-fundamentals/SKILL.md— the 4 non-negotiables (Expo managed, latest SDK, TypeScript, npm) this skill is restoring/advancing.rn-bootstrap/references/stack-defaults.md— the pinned baseline versions used at scaffold time; the diff target for this upgrade.rn-eas-build-submit-update/references/eas-json.md—runtimeVersionpolicy must be re-verified after an SDK bump (a new SDK usually means a new runtime version, which affects EAS Update compatibility).rn-module-add/SKILL.md+rn-backend/references/*— if a wired module depends on a module being deprecated (e.g.expo-avused for audio/video in a wired feature), the breaking-changes step touches that code.
Source of truth — read this before trusting any command below
Expo SDKs ship roughly every quarter and change command surface, deprecations, and config shape release to release. This skill's workflow shape is stable; the exact command flags and package names are not. Before executing anything marked [VERIFY]:
- Check the live Expo docs for the target SDK:
https://docs.expo.dev/workflow/upgrading-expo-sdk-walkthrough/(200 on 2026-08-26) and the changelog athttps://expo.dev/changelog, per-SDK athttps://expo.dev/changelog/sdk-57. ⚠️ Notdocs.expo.dev/changelog— that 404s, and this skill pointed at it in five places until 2026-08-26. A source-of-truth link is worth acurl -o /dev/null -w '%{http_code}'now and then; a dead one sends the reader to guess exactly where you meant them not to. - If MCP tools are available, query MCP Expo (
https://mcp.expo.dev/mcp) for the current upgrade guidance and API status — it is more current than any static doc snapshot. - Cross-check against the official
expo/skillsrepo'sexpo-upgradeskill if installed/available — this skill is modeled on it but does not vendor its per-SDK specifics, since those go stale.
Never silently apply a [VERIFY] command from memory across a major SDK boundary — confirm it against one of the three sources above first.
Workflow
Step 1 — Preconditions + detect current state
Read .workflow/meta.json. Abort if stack.framework != "expo-rn" or phase < "scaffolded".
Read package.json to record the current expo version (this is from_sdk for the history entry). Read app.json/app.config.* for expo.sdkVersion if pinned there too.
Step 2 — Detect CNG vs bare workflow
Check for ios/ and android/ directories at the project root:
- Absent → CNG (Continuous Native Generation): native projects are generated on demand by
expo prebuildat build time.expo prebuild --cleanis not required as part of the upgrade — the nextexpo run:ios/eas buildregenerates them fresh fromapp.json+ config plugins. Skip Step 6 native-rebuild commands entirely. - Present → bare workflow: native folders are checked into the repo and must be regenerated + reinstalled manually. Step 6 applies in full.
See references/native-rebuild.md for the exact per-mode commands and rationale.
Step 3 — Bump Expo and align every dependency
npx expo install expo@latest
npx expo install --check # dry run: which packages are on invalid versions
npx expo install --fix # actually re-resolve them
expo install --fix re-resolves every Expo-adjacent package (react-native, react, expo-router, react-native-reanimated, etc.) to the versions the newly-installed SDK expects — this is the step that actually fixes the dependency graph, not just the expo package itself.
Confirmed at @expo/cli@57.0.18 (npm pack, then the command's own help text): --check — "Check which installed packages need to be updated" — and --fix — "Automatically update any invalid package versions" — both exist. Run --check first on a project you don't own: it tells you the size of the blast radius before you take it.
[VERIFY] on a different SDK major: this flag surface has moved historically (--fix vs interactive-prompt-only), so confirm against the live docs for the target SDK rather than against this line.
Step 4 — Diagnose with expo-doctor
npx expo-doctor
Walk every flagged issue. Common categories: mismatched dependency versions, invalid app.json config plugin entries, native folders out of sync with config (bare only). Do not proceed to Step 5 with unresolved expo-doctor errors — fix them first, re-run until clean (warnings can be triaged, errors cannot).
Step 5 — Clean caches and reinstall
rm -rf node_modules .expo
watchman watch-del-all 2>/dev/null || true
npm install
npx expo install --fix
Stale Metro/Watchman state is a common source of "upgrade worked but the app still crashes" reports — never skip this even if expo-doctor is clean.
Step 6 — Native rebuild (bare workflow only — skip for CNG)
Only if Step 2 detected ios//android/ folders present:
npx expo prebuild --clean
cd ios && pod install --repo-update && cd ..
cd android && ./gradlew clean && cd ..
--repo-update on pod install matters after an SDK bump — the CocoaPods spec repo needs the latest podspecs for the new native module versions. Skipping it is the most common cause of "pod install succeeded but build fails" after an upgrade.
For CNG projects: do nothing here. If the user insists on regenerating native folders locally for debugging, that is a separate, explicit ask — not part of this upgrade flow.
Full rationale + exact per-mode commands: references/native-rebuild.md.
Step 7 — Breaking-changes checklist
Walk references/breaking-changes.md: removed APIs, moved imports, deprecated native modules for the SDK range being crossed. The most common one to check every upgrade: expo-av → expo-audio + expo-video (the AV module was deprecated and split into two focused packages). If the project uses expo-av for camera-adjacent audio/video, migrate before considering the upgrade done.
After migrating any deprecated module, manually test:
- Camera capture (photo + video, if used).
- Audio playback/recording (if used).
- Video playback (if used).
- Navigation (Expo Router route transitions, deep links) — router internals sometimes shift between SDKs.
Step 8 — Beta/preview SDK path (optional, only if explicitly requested)
If the user wants to target a not-yet-stable SDK:
npm view expo dist-tags # ALWAYS first — see below
npx expo install expo@<tag> --fix
- ⚠️
@nextdoes not mean "beta". On 2026-08-26nextandlatestwere the same version (57.0.16), soexpo install expo@nextinstalls stable. Between cycles that is normal. The prerelease channel iscanary/canary-sdk-NN;-preview.Nversions appear during a cycle;sdk-NNtags pin an SDK line. Read the tags, then pick —[VERIFY]againstnpm view expo dist-tagsandhttps://expo.dev/changelog. - Check available runtime versions/manifests via
https://exp.host/--/api/v2/versions(live, 200 on 2026-08-26) when diagnosing beta-channel compatibility issues. - Beta SDKs are inherently less stable — NEVER move a production app onto a prerelease tag without the user's explicit, informed confirmation (this is user-facing risk, not a default). And say which tag you are proposing and what it currently resolves to: "the beta" is not a version.
Full walkthrough (version checks, reverting, third-party compatibility caveats): references/beta-preview.md.
Step 9 — Review release notes and refresh doc links
Read the target SDK's entry on https://expo.dev/changelog (per-SDK pages: https://expo.dev/changelog/sdk-57) end to end — not just the breaking-changes section, since deprecation notices for the next upgrade often appear early. If the project's own docs (.workflow/DESIGN.md, README, code comments) link to version-pinned Expo docs URLs (e.g. docs.expo.dev/versions/v53.0.0/...), update them to the new SDK's version path.
Step 10 — Verify
npx tsc --noEmit
Must pass. Run the app (npx expo start, or a dev build for bare) and manually smoke-test the flows touched in Step 7, plus core navigation.
Step 11 — Update meta.json + commit
Update meta.json:
stack_config.expo_sdk: set to the new SDK version (add this key if not already present).phase: unchanged — this is maintenance, never advance the pipeline phase for an upgrade.history: append{ skill: "rn-upgrade", ran_at: <iso>, inputs: { from_sdk, to_sdk }, outputs: ["package.json", "app.json", ...], phase_before: <phase>, phase_after: <same phase> }.
Commit: chore(deps): upgrade Expo SDK <from> → <to>.
Common anti-patterns (NEVER do)
- ❌ Bump
expowithout immediately runningexpo install --fix— leaves the dependency graph half-upgraded. - ❌ Skip
expo-doctor"because the app still runs" — it catches config-plugin drift that only surfaces at build time. - ❌ Run
expo prebuild --cleanon a CNG project as a matter of habit — unnecessary churn, and can accidentally commit generated native folders that shouldn't be tracked. - ❌ Rebuild native (bare) without
pod install --repo-update— stale CocoaPods specs silently break the build. - ❌ Advance
meta.json#phaseas part of this skill — an upgrade is not scaffolding, a new screen, or a module; the phase must stay exactly where it was. - ❌ Move a project to a beta/
@nextSDK without explicit user confirmation. - ❌ Skip manual camera/audio/video/navigation testing after touching any deprecated native module.
- ❌ Trust a specific command flag from memory across SDK majors — check the
[VERIFY]sources first.
Updating meta.json (recommended pattern)
When this skill modifies state (history appended — no phase change), use the canonical script when available:
# Wherever dev-flow is installed (e.g. ~/.claude/skills/dev-flow/), invoke:
python3 .../dev-flow/scripts/update_meta.py <project-root> append-history \
--skill 'rn-upgrade' --inputs '{"from_sdk":"<x>","to_sdk":"<y>"}' \
--outputs '["package.json","app.json"]' --phase-after <same-phase-as-before>
Note there is deliberately no set-phase call in this skill's usage of the script — phase-after in the history entry must equal phase-before. Fall back to direct JSON editing only if the script is not on PATH (and warn the user), preserving every other field verbatim.
Sources
- Official: https://docs.expo.dev/workflow/upgrading-expo-sdk-walkthrough/
- Official: https://expo.dev/changelog
- Official: https://docs.expo.dev/versions/latest/
- MCP Expo: https://mcp.expo.dev/mcp
- Modeled on the official
expo-upgradeskill from theexpo/skillsrepo (consult it directly for per-SDK specifics — not vendored here since it goes stale).