{"record":{"id":"5e0a958fd08846c7","repo":"stablyai/orca","slug":"pnpm-install-completed-but-node-modules-bin-expo","errorCode":null,"errorMessage":"pnpm install completed, but node_modules/.bin/expo is still missing.","messagePattern":"pnpm install completed, but node_modules/\\.bin/expo is still missing\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"mobile/scripts/mobile-expo-cli.mjs","lineNumber":53,"sourceCode":"  })\n}\n\nexport async function ensureMobileExpoCli(mobileDir, logger = {}) {\n  if (getMobileExpoExecutablePath(mobileDir)) {\n    return\n  }\n\n  const message = 'Mobile dependencies are missing; running pnpm install --frozen-lockfile...'\n  if (logger.logStep) {\n    logger.logStep('deps', message)\n  } else {\n    console.log(`[start] ${message}`)\n  }\n\n  await runPnpmInstall(mobileDir)\n\n  if (!getMobileExpoExecutablePath(mobileDir)) {\n    throw new Error('pnpm install completed, but node_modules/.bin/expo is still missing.')\n  }\n\n  logger.logSuccess?.('Mobile dependencies installed')\n}\n","sourceCodeStart":35,"sourceCodeEnd":58,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/mobile/scripts/mobile-expo-cli.mjs#L35-L58","documentation":"Thrown by the mobile Expo CLI launcher after it auto-runs `pnpm install --frozen-lockfile` in the mobile directory and still cannot locate node_modules/.bin/expo. The script first checks for expo; if missing it installs deps; if expo is STILL missing post-install, it treats the dependency state as broken rather than launching a missing binary.","triggerScenarios":"mobile/node_modules/.bin/expo is absent after pnpm install — e.g. expo is not declared in package.json, the lockfile is out of sync and --frozen-lockfile exits but the script did not catch it, pnpm hoisted expo elsewhere, or the platform-specific expo binary shim failed to link.","commonSituations":"A package.json change that removed expo-cli/expo without updating the lockfile, a corrupted node_modules from a previous interrupted install, a pnpm version mismatch that lays out .bin differently, or running on Windows where .bin/expo is expo.cmd and the path check looks for the wrong suffix.","solutions":["Verify `expo` is present in mobile/package.json dependencies/devDependencies and run `pnpm install` (without --frozen-lockfile) to regenerate the lockfile if needed.","Delete mobile/node_modules and mobile/pnpm-lock.yaml's effect by removing node_modules, then reinstall.","Check getMobileExpoExecutablePath() handles the current OS (look for expo vs expo.cmd vs expo.ps1).","Confirm pnpm is the intended manager and the workspace root's pnpm-workspace.yaml includes mobile."],"exampleFix":"// before\nawait runPnpmInstall(mobileDir)\nif (!getMobileExpoExecutablePath(mobileDir)) {\n  throw new Error('pnpm install completed, but node_modules/.bin/expo is still missing.')\n}\n\n// after — capture install exit code and report it\nconst installResult = await runPnpmInstall(mobileDir)\nif (!getMobileExpoExecutablePath(mobileDir)) {\n  throw new Error(`pnpm install (exit ${installResult.code}) completed, but node_modules/.bin/expo is still missing. Check that 'expo' is in package.json.`)\n}","handlingStrategy":"validation","validationCode":"// Verify expo is declared before attempting install\nimport { readFileSync } from 'node:json'\nconst pkg = JSON.parse(readFileSync(join(mobileDir, 'package.json'), 'utf8'))\nconst hasExpo = Boolean((pkg.dependencies?.expo) || (pkg.devDependencies?.['expo-cli']))\nif (!hasExpo) {\n  throw new Error(\"'expo' is not in mobile/package.json; add it before launching\")\n}","typeGuard":"function expoExecutableExists(mobileDir: string): boolean {\n  return Boolean(getMobileExpoExecutablePath(mobileDir))\n}","tryCatchPattern":"try {\n  await runPnpmInstall(mobileDir)\n} catch (installErr) {\n  throw new Error(`pnpm install failed: ${(installErr as Error).message}`)\n}\nif (!getMobileExpoExecutablePath(mobileDir)) {\n  throw new Error('pnpm install completed, but node_modules/.bin/expo is still missing.')\n}","preventionTips":["Keep expo in package.json and the lockfile in sync.","Make getMobileExpoExecutablePath() cross-platform (expo / expo.cmd / expo.ps1).","Run `pnpm install` non-frozen locally if the lockfile is stale before pushing."],"tags":["mobile","expo","pnpm","dependencies","tooling"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}