Description
react-router dev can crash when another process runs react-router typegen at the same time. The failure is a filesystem race on .react-router/types:
Error: ENOTEMPTY: directory not empty, rmdir '.react-router/types/app'
This happens because the dev-server typegen watcher and a standalone typegen invocation both try to remove and rewrite the same generated directories concurrently.
Reproduction
-
Start the dev server in a Framework Mode app:
-
While dev is running, trigger typegen from another process (common in real projects):
Or run a script that shells out to typegen, e.g.:
"tsc": "react-router typegen && tsgo"
with a pre-commit hook that also runs react-router typegen before tsc.
-
Repeat a few times or trigger a route-config change in dev while typegen is running.
Expected: Both processes complete; generated types stay consistent.
Actual: Dev server or typegen crashes with ENOTEMPTY when one process rmdirs .react-router/types/app (or the whole types/ tree) while the other is still writing.
Root cause
In @react-router/dev typegen/index.ts:
run() and watch() call fs.rm(typesDirectory, { recursive: true }) before writing
watch() also calls clearRouteModuleAnnotations() on route-config changes, which removes types/<appDirectory>/
- There is no cross-process coordination between the Vite plugin watcher and CLI
typegen
Proposed fix
- Serialize typegen writes with a cross-process lock outside the generated tree (e.g.
.react-router/.typegen.lock), so dev watcher and CLI typegen cannot overlap destructive writes
- Retry
fs.rm on retryable errors (ENOTEMPTY, EBUSY, EPERM) with short backoff before surfacing the error
Environment
@react-router/dev: 8.1.0
- Node: >= 22
- OS: macOS (also reported on other platforms with parallel file operations)
Happy to open a PR with lock + retry if this approach looks good.
Description
react-router devcan crash when another process runsreact-router typegenat the same time. The failure is a filesystem race on.react-router/types:This happens because the dev-server typegen watcher and a standalone
typegeninvocation both try to remove and rewrite the same generated directories concurrently.Reproduction
Start the dev server in a Framework Mode app:
While dev is running, trigger typegen from another process (common in real projects):
Or run a script that shells out to typegen, e.g.:
with a pre-commit hook that also runs
react-router typegenbeforetsc.Repeat a few times or trigger a route-config change in dev while typegen is running.
Expected: Both processes complete; generated types stay consistent.
Actual: Dev server or typegen crashes with
ENOTEMPTYwhen one processrmdirs.react-router/types/app(or the wholetypes/tree) while the other is still writing.Root cause
In
@react-router/devtypegen/index.ts:run()andwatch()callfs.rm(typesDirectory, { recursive: true })before writingwatch()also callsclearRouteModuleAnnotations()on route-config changes, which removestypes/<appDirectory>/typegenProposed fix
.react-router/.typegen.lock), so dev watcher and CLI typegen cannot overlap destructive writesfs.rmon retryable errors (ENOTEMPTY,EBUSY,EPERM) with short backoff before surfacing the errorEnvironment
@react-router/dev: 8.1.0Happy to open a PR with lock + retry if this approach looks good.