Skip to content

dependencies is not iterable / Cannot read property 'reduce' of undefined: FileStore serves zero-prefixed cache files as hits #1961

Description

@robhogan

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:

We should treat a cached value that isn't a valid transform result as a miss, and overwrite it.

Activity

  1. frankleng commented on Oct 8, 2026

    @frankleng

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions