0025 — LTS / stable deps only, no bleeding-edge
0025 — LTS / stable deps only, no bleeding-edge
Section titled “0025 — LTS / stable deps only, no bleeding-edge”Status: Accepted Date: 2026-04-21 Related: project_lts_deps_only memory
Context
Section titled “Context”By 2026-04-21 the Ideony monorepo has ~280 runtime deps and ~90 dev deps across apps/{api,mobile} + packages/*. At this scale a single mis-pinned transitive can take the whole app down — and has, twice this week:
-
pretty-format@30.3.0crash (this commit) — Expo’s@expo/metro-runtime@5.0.4HMRClient callsrequire("pretty-format")at bundle time with no declared dep. pnpm hoisted the30.3.0copy pulled in by@testing-library/react-native@13.3.3. v30 shipsbuild/index.mjswithexport default cjsModule.default, which — once Metro’s babel transform rewrites it — evaluates toundefinedbecause_interopDefaultleaves the CJS exports object untouched (it has__esModule: true), socjsModule.defaultis theformatfunction and.default.defaultdoes not exist. Every web page blanked withTypeError: Cannot read properties of undefined (reading 'default')before React could mount. Fixed by Metro resolver redirect to the v29.7.0 CJS-only copy that Expo itself links. -
expo-router@5.0.7vs@expo/router-server@55.0.14skew — router-server expectsexpo-router/internal/routingfromexpo-router@55.0.12, but Expo SDK 55 pins the5.0.7release. Workaround:experiments.typedRoutes: falseinapps/mobile/app.json. Real fix (post-demo) is to align the two versions.
Both bugs came from non-LTS / mismatched-major versions propagating into the workspace.
Decision
Section titled “Decision”Every dep added or upgraded in Ideony MUST be on a stable / LTS / GA channel. No next-tag, canary, beta, or rc releases, even when the latest stable has a known bug.
Pins (current state, enforced via lockfile + PR review):
- Node.js — 22.x LTS (
engines.node >=22.0.0) - Expo SDK — 55 stable
- React / React Native — whatever Expo SDK 55 declares as peer; never force-upgrade above
- NestJS — 11 stable
- Prisma — 7.x stable
- Test libs (Jest / Vitest / Playwright /
@testing-library/*) — minor-pin; reject major bumps that pull breaking transitives - pnpm 9.15.4, Turbo 2.x, Biome 2.x — stable minor
Pre-upgrade procedure (mandatory):
- Fetch docs via Context7 MCP — confirm version is tagged stable / GA / LTS (never
next,canary,beta,rc). - Cross-check Exa MCP for open CVEs / migration warnings on that version.
- Inspect
peerDependencies— reject if it pulls a canary or breaking transitive. - If a peer conflict forces a non-LTS, add
pnpm.overridesin rootpackage.jsonand cite this ADR in the override comment — never silently accept the hoisted non-LTS version. - If a dep ships an ESM-only
.mjsMetro cannot digest, add a resolver redirect inapps/mobile/metro.config.jsto the last CJS-only version rather than accept the runtime break.
Major upgrades: only post-launch, only via a new ADR that documents migration + risk, only with full Docker + E2E verification pass before merge.
Consequences
Section titled “Consequences”Positive
- Predictable bug surface for the remaining pre-launch push.
- No more “the lockfile silently pulled a broken transitive” post-mortems.
- Metro resolver redirects become a documented workaround pattern rather than ad-hoc patches.
Negative
- Some test libs will lag behind the latest features for a release cycle (accepted — we prioritize shipping over cutting-edge testing tooling).
pnpm.overridesblock in rootpackage.jsonwill grow — every entry must link back to an ADR.
Neutral
- Security patches still land on schedule (stable-channel updates include CVE fixes); this policy blocks feature-chasing, not security patches.
Alternatives considered
Section titled “Alternatives considered”- “Upgrade always, fix breakage when it happens.” Rejected — this is the status quo that produced the two bugs cited in Context. Bug budget pre-launch is zero.
- “Pin lockfile manually per dep, no policy.” Rejected — a policy is cheaper than a per-upgrade argument, and a new dev (or LLM agent) needs written guidance.
- “Auto-update bot (Renovate / Dependabot).” Deferred post-launch. Premature in MVP 0 where a single bad minor bump blocks a demo.
Implementation
Section titled “Implementation”- This ADR lands alongside the
pretty-format@30Metro resolver redirect. CLAUDE.md“Dependency policy” section added as top-level rule.- Memory file
feedback_lts_deps_only.mdadded to session memory so future agents apply the rule without re-asking. - No immediate lockfile changes — existing pinned versions already satisfy the policy; the only outlier is
pretty-format@30.3.0which stays installed for@testing-library/react-native@13.3.3tests but is diverted at the Metro bundle level on web.