Description
On the New Architecture, an incremental iOS build fails with a dependency cycle involving ReactCodegen:
error: Cycle in dependencies between targets 'ReactCodegen' and '<SomePod>'; building could produce unreliable results.
Cycle path: ReactCodegen → <SomePod> → ReactCodegen
The failure is intermittent: a clean build passes, but every subsequent (incremental) build fails. This makes it look non-deterministic and hard to reproduce.
This commonly surfaces when building with Detox (whose config sets -derivedDataPath inside the project, e.g. ios/build), while a normal react-native run-ios (which uses Xcode's default DerivedData outside the repo) is unaffected.
Root cause
getInputFiles() in scripts/codegen/generate-artifacts-executor/generateReactCodegenPodspec.js emits a directory path for the [CP-User] Generate Specs script phase's input_files instead of an empty list, when an app's codegenConfig.jsSrcsDir contains no files matching the spec naming convention (Native*.ts/Native*.js/*NativeComponent.ts/*NativeComponent.js).
const list = String(execSync(findCommand))
.trim()
.split('\n') // when `find` finds nothing → [""], not []
.sort()
.map(filepath => `"\${PODS_ROOT}/${path.relative(xcodeproj, filepath)}"`)
.join(',\n');
return `[${list}]`;
When find matches no spec files, "".split('\n') yields [""], so .map runs once with filepath === "". path.relative(xcodeproj, "") resolves to a relative path to the app root (e.g. ..), so input_files becomes ["${PODS_ROOT}/.."] — a directory. That directory contains the iOS build output (e.g. ios/build). The pods producing those artifacts depend on ReactCodegen, so Xcode detects a cycle. On a clean build the output dir doesn't exist yet, so the cycle isn't triggered.
Steps to reproduce
On any New Architecture app:
- In
package.json, set codegenConfig.jsSrcsDir to a directory that contains no Native*/*NativeComponent files (e.g. an empty specs/ folder, or one with specs that don't follow the naming convention):
"codegenConfig": { "name": "MySpec", "type": "modules", "jsSrcsDir": "specs" }
cd ios && pod install.
- Inspect
build/generated/ios/ReactCodegen.podspec → notice 'input_files' => ["${PODS_ROOT}/.."] (a directory) instead of [].
- Build twice with an in-project derived data path:
xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp \
-configuration Debug -sdk iphonesimulator -derivedDataPath ios/build
- 1st build: passes.
- 2nd (incremental) build: fails with
Cycle in dependencies between targets 'ReactCodegen' and '<Pod>'.
Expected behavior
Both the clean and the incremental build succeed. With the fix, input_files is [] when no spec files match, so no directory dependency is created and there is no cycle.
Suggested fix
Filter empty entries so an empty find result yields []:
const list = String(execSync(findCommand))
.trim()
.split('\n')
+ .filter(Boolean)
.sort()
Behavior is unchanged when real Native* spec files are present (their paths are truthy and are kept).
React Native Version
0.82.1 (also reproducible on main).
Affected platforms
Output of npx react-native info
Not platform-tooling specific. Reproduces with Xcode 16/26 on macOS with New Architecture enabled and an in-project -derivedDataPath (e.g. via Detox).
Related PR
Fix: #57759
Description
On the New Architecture, an incremental iOS build fails with a dependency cycle involving
ReactCodegen:The failure is intermittent: a clean build passes, but every subsequent (incremental) build fails. This makes it look non-deterministic and hard to reproduce.
This commonly surfaces when building with Detox (whose config sets
-derivedDataPathinside the project, e.g.ios/build), while a normalreact-native run-ios(which uses Xcode's default DerivedData outside the repo) is unaffected.Root cause
getInputFiles()inscripts/codegen/generate-artifacts-executor/generateReactCodegenPodspec.jsemits a directory path for the[CP-User] Generate Specsscript phase'sinput_filesinstead of an empty list, when an app'scodegenConfig.jsSrcsDircontains no files matching the spec naming convention (Native*.ts/Native*.js/*NativeComponent.ts/*NativeComponent.js).When
findmatches no spec files,"".split('\n')yields[""], so.mapruns once withfilepath === "".path.relative(xcodeproj, "")resolves to a relative path to the app root (e.g...), soinput_filesbecomes["${PODS_ROOT}/.."]— a directory. That directory contains the iOS build output (e.g.ios/build). The pods producing those artifacts depend onReactCodegen, so Xcode detects a cycle. On a clean build the output dir doesn't exist yet, so the cycle isn't triggered.Steps to reproduce
On any New Architecture app:
package.json, setcodegenConfig.jsSrcsDirto a directory that contains noNative*/*NativeComponentfiles (e.g. an emptyspecs/folder, or one with specs that don't follow the naming convention):cd ios && pod install.build/generated/ios/ReactCodegen.podspec→ notice'input_files' => ["${PODS_ROOT}/.."](a directory) instead of[].Cycle in dependencies between targets 'ReactCodegen' and '<Pod>'.Expected behavior
Both the clean and the incremental build succeed. With the fix,
input_filesis[]when no spec files match, so no directory dependency is created and there is no cycle.Suggested fix
Filter empty entries so an empty
findresult yields[]:const list = String(execSync(findCommand)) .trim() .split('\n') + .filter(Boolean) .sort()Behavior is unchanged when real
Native*spec files are present (their paths are truthy and are kept).React Native Version
0.82.1 (also reproducible on
main).Affected platforms
Output of
npx react-native infoNot platform-tooling specific. Reproduces with Xcode 16/26 on macOS with New Architecture enabled and an in-project
-derivedDataPath(e.g. via Detox).Related PR
Fix: #57759