Skip to content

@nuxthub/core race: blob shim overwritten #830

@onmax

Description

@onmax

Title: @nuxthub/core race condition: concurrent Nuxt runs overwrite generated @nuxthub/blob shim (wrong driver / can corrupt file)

Summary

When two Nuxt commands run concurrently in the same project (e.g. nuxt typecheck and nuxt build), @nuxthub/core writes a generated physical package to node_modules/@nuxthub/blob/ from both processes. Because the path is shared, concurrent runs race and the contents are non-deterministic.

In practice this can cause:

  • Wrong driver: after a Cloudflare build, the generated shim ends up as the fs driver variant.
  • Corruption: the generated blob.mjs can end up partially written / interleaved, causing bundler parse errors.

Why this is a bug

  • node_modules/@nuxthub/blob/blob.mjs is written during module setup and is shared across processes.
  • Concurrent writes are not atomic, so output depends on timing and can be corrupted.
  • nuxt typecheck should not mutate shared runtime shims in node_modules/.

Code pointer

The physical shim is generated during module setup:

  • src/blob/setup.ts (setupBlob()): writes node_modules/@nuxthub/blob/blob.mjs, blob.d.ts, package.json.

Expected

After NITRO_PRESET=cloudflare-module nuxt build, the generated blob shim should be stable and match Cloudflare (R2) configuration (or at least not be overwritten by a concurrent nuxt typecheck).

Actual

Within a few attempts, blob.mjs ends up as the fs driver variant after build:

import { createBlobStorage } from "@nuxthub/core/blob";
import { createDriver } from "@nuxthub/core/blob/drivers/fs";

export { ensureBlob } from "@nuxthub/core/blob";
export const blob = createBlobStorage(createDriver({"dir":"/path/to/.data/blob"}));

Environment

  • macOS
  • Node v25.6.0
  • pnpm 10.29.2
  • nuxt 4.3.1
  • nitro 2.13.1
  • @nuxthub/core 0.10.6

Fix direction

  • Avoid generating shims into the project node_modules/ during module setup (especially for typecheck/dev).
  • If a physical package is needed for external bundlers/workflows, generate it only during build hooks.
  • Make writes atomic (tmp + rename) to avoid partial/corrupted files.

Repro repo

StackBlitz (auto install + run):

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