Skip to content

Pyodide NODEFS filesystem writes fail with invalid length error #30972

@mlilback

Description

@mlilback

Bug Report: Pyodide NODEFS Filesystem Writes Fail in Deno

Summary

Pyodide's NODEFS filesystem mount succeeds in Deno, but file write operations fail with OSError: raw write() returned invalid length. The same code works correctly in Node.js, indicating a Deno-specific incompatibility with Emscripten's NODEFS implementation.

Status: Confirmed still broken in Deno 2.5.4 (latest stable release as of October 2025)

Environment

  • Deno version: 2.5.4 (latest stable as of 2025-10-10)
  • Platform: macOS 15.6.1 (darwin aarch64)
  • Pyodide version: 0.27.5
  • V8 version: 14.0.365.5-rusty
  • TypeScript version: 5.9.2

Expected Behavior

When mounting a host directory to Pyodide's virtual filesystem using NODEFS and writing files through Python, the files should be written to the host filesystem successfully (as they are in Node.js).

Actual Behavior

The NODEFS mount succeeds without error, but attempting to write files through Python results in:

OSError: raw write() returned invalid length 3285164 (should have been between 0 and 13)

The error shows raw write() returning an impossibly large value (3,285,164 bytes) when trying to write a small string (13 bytes: "Hello, World!").

Minimal Reproduction

Save the following as nodefs-bug-reproduction.ts:

/**
 * Minimal reproduction of NODEFS write failure in Deno
 *
 * Expected: File writes through Pyodide's NODEFS-mounted filesystem should succeed
 * Actual: OSError: raw write() returned invalid length (much larger than expected)
 *
 * This works correctly in Node.js but fails in Deno.
 */

import { loadPyodide } from "npm:[email protected]";

console.log("Deno version:", Deno.version);
console.log("Platform:", Deno.build.os, Deno.build.arch);
console.log();

// Create temporary directory on host filesystem
const tempDir = await Deno.makeTempDir();
console.log("Host directory:", tempDir);

// Load Pyodide
console.log("Loading Pyodide...");
const pyodide = await loadPyodide({ fullStdLib: false });
console.log("Pyodide version:", pyodide.version);
console.log();

// Create virtual directory in Pyodide
pyodide.FS.mkdir('/test');

// Attempt to mount NODEFS
console.log("Mounting NODEFS...");
try {
    pyodide.FS.mount(
        pyodide.FS.filesystems.NODEFS,
        { root: tempDir },
        '/test'
    );
    console.log("✓ NODEFS mount succeeded");
} catch (error) {
    console.error("✗ NODEFS mount failed:", error);
    await Deno.remove(tempDir, { recursive: true });
    Deno.exit(1);
}

// Attempt to write a file through Python
console.log("\nWriting file through Python...");
try {
    pyodide.runPython(`
with open('/test/hello.txt', 'w') as f:
    f.write('Hello, World!')
print('✓ File written successfully')
    `);

    // Verify file on host
    const content = await Deno.readTextFile(`${tempDir}/hello.txt`);
    console.log("✓ File verified on host:", content);
    console.log("\n✓ SUCCESS: NODEFS is working!");
} catch (error) {
    console.error("\n✗ FAILURE: File write failed");
    console.error("Error type:", error.constructor.name);
    console.error("Error message:", error.message);
    console.error("\nFull error:", error);
}

// Cleanup
await Deno.remove(tempDir, { recursive: true });

Run with:

deno run --allow-all nodefs-bug-reproduction.ts

Output

Deno version: { deno: "2.5.4", v8: "14.0.365.5-rusty", typescript: "5.9.2" }
Platform: darwin aarch64

Host directory: /var/folders/c4/9615f6ks2jb48xrjyt7scrd40000gn/T/8bbca84df330364
Loading Pyodide...
Pyodide version: 0.27.5

Mounting NODEFS...
✓ NODEFS mount succeeded

Writing file through Python...

✗ FAILURE: File write failed
Error type: PythonError
Error message: OSError: raw write() returned invalid length 3285164 (should have been between 0 and 13)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/lib/python312.zip/_pyodide/_base.py", line 523, in eval_code
    .run(globals, locals)
     ^^^^^^^^^^^^^^^^^^^^
  File "/lib/python312.zip/_pyodide/_base.py", line 357, in run
    coroutine = eval(self.code, globals, locals)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<exec>", line 2, in <module>
OSError: raw write() returned invalid length 3285164 (should have been between 0 and 13)

Analysis

The issue appears to be in how Deno implements the fs.write() or fs.writeSync() functions that Emscripten's NODEFS relies on. The returned byte count is nonsensical (3,285,164 instead of 13), suggesting:

  1. A return value mismatch between what NODEFS expects and what Deno provides
  2. Possible incorrect type coercion or promise handling
  3. Memory address being returned instead of byte count

This is particularly problematic because:

  • NODEFS is the only Pyodide filesystem that bridges virtual FS to host FS in non-browser environments
  • Without it, files written in Pyodide are invisible to the host process (requiring manual extraction)
  • This significantly impacts any Deno application using Pyodide for Python code execution with file I/O

Related Issues

This issue has been reported before but appears unresolved:

Workaround

We've implemented a workaround by:

  1. Using Pyodide's default MEMFS (in-memory filesystem)
  2. Extracting files from Pyodide's virtual FS to host FS after Python execution completes
  3. Using pyodide.FS.readFile() and Deno.writeFile() for manual synchronization

This works but adds complexity and requires careful file lifecycle management.

Request

Can the Deno team investigate the incompatibility between Deno's filesystem implementation and Emscripten's NODEFS expectations? Specifically:

  1. What does Deno's fs.write()/fs.writeSync() return that differs from Node.js?
  2. Can Deno's implementation be adjusted to be compatible with Emscripten's expectations?
  3. If not, should Deno document this limitation clearly?

This would enable proper Pyodide filesystem integration in Deno without manual workarounds.

Additional Testing

Willing to provide additional test cases or debugging information if helpful. Can also test on Linux if needed to confirm this is not macOS-specific.


Priority: Medium-High - Affects any Deno application using Pyodide with file I/O

Impact: Workarounds exist but require significant additional code and complexity

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions