{"record":{"id":"6e0afcbfcd66c898","repo":"expo/expo","slug":"dylib-not-found-at-binarypath-build-it-first-b","errorCode":null,"errorMessage":"Dylib not found at ${binaryPath}. Build it first by running: cd ${path.dirname(path.join(frameworkPath, '..'))} && ./scripts/build.sh","messagePattern":"Dylib not found at (.+?)\\. Build it first by running: cd (.+?) && \\./scripts/build\\.sh","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"apps/bare-expo/e2e/image-comparison/inspector/ScreenInspectorIOS.ts","lineNumber":47,"sourceCode":"  bounds?: {\n    x: number;\n    y: number;\n    width: number;\n    height: number;\n  };\n  path?: string;\n  width?: number;\n  height?: number;\n  error: string;\n}\n\nexport function getDylibPath(): string {\n  const frameworkName = 'IOSScreenInspectorFramework.framework';\n  const frameworkPath = path.resolve(__dirname, 'bin', frameworkName);\n  const binaryPath = path.join(frameworkPath, 'IOSScreenInspectorFramework');\n\n  if (!fs.existsSync(binaryPath)) {\n    throw new Error(\n      `Dylib not found at ${binaryPath}. Build it first by running: cd ${path.dirname(path.join(frameworkPath, '..'))} && ./scripts/build.sh`\n    );\n  }\n\n  return binaryPath;\n}\n\nexport class ScreenInspectorIOS {\n  private requestPipePath = '/tmp/ios_screen_inspector_request';\n  private responsePipePath = '/tmp/ios_screen_inspector_response';\n\n  async getCoordinates(\n    accessibilityId: string,\n    timeoutMs: number = 15000\n  ): Promise<{\n    x: number;\n    y: number;\n    width: number;","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/expo/expo/blob/b09195aac27c9e4a63daaf12dcb6f00b6b2e08ee/apps/bare-expo/e2e/image-comparison/inspector/ScreenInspectorIOS.ts#L29-L65","documentation":"Thrown by getDylibPath() when the compiled IOSScreenInspectorFramework binary is not present at the expected path inside the e2e/image-comparison/inspector/bin directory. This framework is an out-of-tree native dylib that the iOS simulator screen inspector injects into the app via DYLD_INSERT_LIBRARIES. The error is a hard precondition gate: nothing in ScreenInspectorIOS can work until the binary is built.","triggerScenarios":"Calling getDylibPath() (directly, or transitively through ScreenInspectorIOS.getCoordinates / captureView / startSimulatorAppWithDylib) on a fresh checkout, after a clean, after switching branches that touch the framework, or on a CI runner where the build step was skipped or failed. The check is fs.existsSync(binaryPath) failing on the framework's executable file (not the .framework bundle).","commonSituations":"Running e2e image-comparison tests for the first time without running the build script; the framework was .gitignored so a clone lacks it; a previous ./scripts/build.sh failed silently; running on a non-macOS host where the framework cannot be compiled at all.","solutions":["Run the exact build command the message prints: cd into path.dirname of the parent of bin (the inspector package root) and execute ./scripts/build.sh to compile the framework.","Confirm the binary now exists at the path printed in the error (the IOSScreenInspectorFramework executable inside bin/IOSScreenInspectorFramework.framework), not just the .framework directory.","If build.sh fails, run it directly and read its output — it typically requires Xcode command line tools and a valid iOS SDK; ensure xcode-select points at the active developer dir.","On non-macOS hosts, these tests are unsupported — gate them behind a darwin platform check rather than letting getDylibPath throw."],"exampleFix":"// before: tests run on any platform and throw at getDylibPath()\nif (process.platform !== 'darwin') {\n  throw new Error('iOS screen inspector e2e requires macOS');\n}\n// after: ensure the framework is built before constructing the inspector\nimport { execSync } from 'child_process';\nimport { existsSync } from 'fs';\nimport path from 'path';\n\nconst frameworkBinary = path.resolve(__dirname, 'bin', 'IOSScreenInspectorFramework.framework', 'IOSScreenInspectorFramework');\nif (!existsSync(frameworkBinary)) {\n  execSync('./scripts/build.sh', { stdio: 'inherit', cwd: path.resolve(__dirname, '..') });\n}","handlingStrategy":"validation","validationCode":"import { existsSync } from 'fs';\nimport path from 'path';\n\nconst frameworkBinary = path.resolve(__dirname, 'bin', 'IOSScreenInspectorFramework.framework', 'IOSScreenInspectorFramework');\nconst isDylibBuilt = existsSync(frameworkBinary);\nif (!isDylibBuilt) {\n  throw new Error(`Framework not built. Run ./scripts/build.sh in ${path.resolve(__dirname, '..')}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Add a pretest npm script that runs scripts/build.sh so the framework exists before any e2e target invokes getDylibPath.","Gate iOS e2e behind process.platform === 'darwin' to fail fast on unsupported hosts.","Commit a CI step that verifies the binary exists immediately after the build step, failing the job there rather than deep in a test."],"tags":["ios","native-build","e2e","filesystem","macos"],"backgroundTag":null,"analyzedSha":"b09195aac27c9e4a63daaf12dcb6f00b6b2e08ee","analyzedAt":"2026-08-12T15:59:58.304Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}