You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
dependencies is not iterable / Cannot read property 'reduce' of undefined: FileStore serves zero-prefixed cache files as hits #1961
This is a transform cache bug with a reliable reproduction on Windows NTFS in particular.
FileStore.get returns any file starting with a null byte as a Buffer (that's how set marks Buffer values), so a zero-prefixed cache file is a truthy hit. Transformer.transformFile uses it without validating content, and the build fails with dependencies is not iterable (Cannot read property 'reduce' of undefined on older versions) until the cache is cleared.
This is a symptom of power loss on NTFS - the file length is journalled but the data isn't flushed, so a subsequent read comes back full of nulls (e.g. coollabsio/jean#679). That fits the reports, all on Windows:
There is a second way to get a zero-prefixed entry that does not need power loss or Windows: two writers of the same key in one process.
FileStore.set rewrites the entry in place with fs.promises.writeFile, which opens the file with O_TRUNC and then writes it in 512 KiB chunks. If another set for the same key truncates the file while the first writer is between chunks, the first writer's next chunk lands at its old offset and the file now starts with a run of zero bytes. A get in that window sees data[0] === 0, returns the rest as a Buffer, and Transformer fails with TypeError: dependencies is not iterable. Readers can also see truncated JSON, which is a silent miss.
We hit this on Linux in a React Native app using Expo DOM components: expo export bundles every DOM component in parallel in one process, and those bundles transform shared modules under the same cache key at the same time. With metro-cache 0.84.5 the export failed in 8 of 16 runs; the bad value was a Buffer of 524,287 zero bytes (one 512 KiB chunk minus the marker byte). @expo/metro-config's FileStore extends this one, so Expo users get it too. The code is the same on main.
Standalone stress test on main (ext4, Node 24): 8 concurrent set+get loops on one key with a 2 MB value, 16 runs. The current store returned truncated JSON in 16/16 runs and a zero-prefixed Buffer hit in 1/16. Writing to a temp file and renaming gave 0 bad reads.
I opened #2040, which writes each entry to a unique temp file in the same directory and renames it into place, so readers see the old entry or the new one, never a partial one. It keeps the on-disk format and stays inside FileStore. It does not cover the NTFS power-loss case (that would still need get to reject a bad entry, or an fsync before rename), so the two fixes are complementary.
Activity
frankleng commentedon Oct 8, 2026
There is a second way to get a zero-prefixed entry that does not need power loss or Windows: two writers of the same key in one process.
FileStore.setrewrites the entry in place withfs.promises.writeFile, which opens the file withO_TRUNCand then writes it in 512 KiB chunks. If anothersetfor the same key truncates the file while the first writer is between chunks, the first writer's next chunk lands at its old offset and the file now starts with a run of zero bytes. Agetin that window seesdata[0] === 0, returns the rest as aBuffer, andTransformerfails withTypeError: dependencies is not iterable. Readers can also see truncated JSON, which is a silent miss.We hit this on Linux in a React Native app using Expo DOM components:
expo exportbundles every DOM component in parallel in one process, and those bundles transform shared modules under the same cache key at the same time. With metro-cache 0.84.5 the export failed in 8 of 16 runs; the bad value was aBufferof 524,287 zero bytes (one 512 KiB chunk minus the marker byte).@expo/metro-config'sFileStoreextends this one, so Expo users get it too. The code is the same onmain.Standalone stress test on main (ext4, Node 24): 8 concurrent
set+getloops on one key with a 2 MB value, 16 runs. The current store returned truncated JSON in 16/16 runs and a zero-prefixedBufferhit in 1/16. Writing to a temp file and renaming gave 0 bad reads.I opened #2040, which writes each entry to a unique temp file in the same directory and renames it into place, so readers see the old entry or the new one, never a partial one. It keeps the on-disk format and stays inside
FileStore. It does not cover the NTFS power-loss case (that would still needgetto reject a bad entry, or an fsync before rename), so the two fixes are complementary.