Skip to main content

Expo Doctor — monorepo assessment

Tier: Internal
Status: Active — Path A chosen (2026-06-07)
Audience: native maintainers, CI authors
Related: Cursor plan expo_doctor_fixes (2026-06-07), DEVELOPMENT-NATIVE-LOCAL.md, native-eas-demo-build-log.md


1. Summary

npx expo-doctor (18 checks) must pass from the primary native app in the active installation profile after root npm ci — core profile: apps/native/blank/; reference profile: apps/native/brewery/. check-native-expo-doctor.sh resolves the app from .umbraculum/install*.json. Same context as EAS (native-eas-build.yml) and native-deps.yml.

Baseline (2026-06-07, pre-remediation): 16/18 — two failures:

CheckFailure
Expo configapp.config.js not using app.json values (doctor heuristic)
Duplicate dependenciesexpo@54 (brewery) vs expo@56 (root peer), react@19.1 vs react@19.2.4, stale apps/native/node_modules/ tree

Decision: Path A (shipped 2026-06-07) — keep Expo Go + react@19.1.0; align apps/web to 19.1.0 (monorepo-wide override); root expo@54.0.35 override; tighten @umbraculum/native-shell peer ranges; post-npm ci cleanup via check-native-expo-doctor.sh. Path B deferred.


2. Reproduce

cd $REPO_ROOT
docker run --rm -v "$PWD:/repo" -w /repo node:20-slim \
bash -lc "npm ci --no-audit --no-fund && cd apps/native/brewery && npx expo-doctor@latest"

EAS prep (native-eas-build.yml): root npm ci./scripts/build-packages-in-docker.sheas build with working-directory: apps/native/brewery.


3. Layout (post–RFC-0011 Wave 4A)

PathRole
apps/native/blank/@umbraculum/native-blank — blank Expo app (core installation profile; CI expo-doctor default)
apps/native/brewery/@umbraculum/native-brewery — Expo SDK 54 app (reference profile; EAS, Metro, screens)
apps/native/Umbrella index only — must not retain node_modules/ after Wave 4A
packages/platform/native-shell/@umbraculum/native-shell — shared auth/i18n/bootstrap (file: dep)
Repo root node_modules/Hoisted monorepo stack (react@19.1.0 via overrides; phantom expo@56 / react@19.2.x before Path A remediation)

4. npm why findings (2026-06-07)

expo@56.0.9 at repo root (peer: true)

  • Trigger: expo-constants@~18.0.0 in @umbraculum/native-shell devDependencies (and brewery deps) hoisted to root; npm 10 satisfies peer expo with latest (56.x).
  • Brewery app correctly installs expo@~54.0.35 under apps/native/brewery/node_modules/expo.
  • Fix (Path A): root package.json overrides.expo: "~54.0.35" collapses phantom expo@56 tree.

react alignment (post–Path A)

SourceVersionNotes
Root package.json overrides19.1.0Collapses web + native to Expo Go ABI
apps/web/package.json19.1.0Was 19.2.4 pre-remediation
apps/native/brewery/package.json19.1.0 exactExpo Go ABI
metro.config.jsBrewery node_modules/reactMust not pin root hoisted copy

Do not bump apps/web or root overrides to 19.2.x without Path B (DEVELOPMENT-NATIVE-LOCAL.md § Long-term). Historical pre-remediation drift (19.2.4 web vs 19.1.0 native) is what doctor duplicate checks caught.

react-native@0.84.0 at root

  • Child of expo@56 peer tree only — removed when expo override pins SDK 54.

Stale apps/native/node_modules/

  • Leftover from pre–Wave 4A when Expo lived at apps/native/. No apps/native/package.json workspace; lockfile entries marked extraneous.
  • Doctor reported a third copy of expo@54 at ../node_modules/expo.
  • Fix: remove directory; do not recreate; CI/docker prep step documents cleanup.

5. Path A vs Path B

Path A (chosen)Path B (deferred)
Expo GoKeep as primary dev loopCustom dev client or SDK upgrade
React nativeStay 19.1.0 in breweryAlign to 19.2.4 monorepo-wide
SDKStay Expo 54Expo 55+ spike
DoctorConfig fix + overrides + stale tree cleanupFull alignment milestone
RiskLow — documented monorepo hoist for web react if residualBreaks Expo Go until dev-client workflow lands

6. Remediation shipped (this epic)

StepChange
Phase 1apps/native/brewery/app.config.js({ config }) => spread pattern
Phase 2aRoot overrides (expo, expo-font, react, react-dom, react-native, react-native-svg); apps/webreact@19.1.0; @umbraculum/native-shell peer pins; @umbraculum/ui react-native-svg@15.12.1 exact; Metro React pin from brewery node_modules
Phase 2a cleanupscripts/check-native-expo-doctor.sh — rm stale apps/native/node_modules, packages/platform/ui/node_modules, phantom root expo-font only (keep hoisted root react-native-svg@15.12.1)
Phase 3native-deps.yml runs check-native-expo-doctor.sh (manifest-selected app + typecheck); AGENTS.md + EAS-DEMO-SETUP.md agent gates
2026-06Installation profile: core → blank (was starter), reference → brewery; see installation-profile.md

7. Doctor status

DateScoreNotes
2026-06-07 pre16/18Config + duplicates
2026-06-07 post18/18After remediation + cleanup script

Update native-eas-demo-build-log.md § Expo Doctor when green.


8. Agent guardrails (do not regress)

Future agents must treat this doc + the toolset gate as binding — not optional tribal knowledge.

SurfaceGuard
Cursor rule78-native-expo-doctor-monorepo-gate (umbraculum-node-react-cursor-assistant) — globs apps/native/**, native-shell, root lockfiles
Cursor skillnative-expo-doctor-pre-push — reproduces GHA native-deps.yml before push
Repo interceptorAGENTS.md § Native dependency / expo-doctor gate
T2 verifynpm run verify:pre-push runs expo-doctor native step when .umbraculum/gha-trigger-map.json matches native-deps paths

Hard do-nots: loosen native-shell expo peer to >=54; add expo-* to native-shell devDependencies; bump web React without Path B; skip check-native-expo-doctor.sh after override/lockfile edits; revert app.config.js away from ({ config }) => spread.