Skip to content

Python edits to filesystem.writeFile-created files are reverted by shadow reconciliation #2022

Description

@ankssjain

Problem

Python edits to a file seeded with vm.filesystem.writeFile() can succeed and be visible inside the Python execution, then silently revert to the API-seeded bytes when the host reads the file after execution.

This is the Python VFS bridge counterpart of #1961. The JavaScript fix in #1981 is present in the tested source, but Python uses a different write handler. Related: the shell/WASM report #1901 is still open. This report is specifically about Python writes, not the shell-to-Python child-process issue #2012.

Reproduction

With a VM in which Python execution is available:

await vm.filesystem.mkdir('/workspace', { recursive: true });
await vm.filesystem.writeFile(
  '/workspace/data.txt', new TextEncoder().encode('original\n'),
);

const result = await vm.python.execute(`
from pathlib import Path
p = Path('/workspace/data.txt')
p.write_text('EDITED\\n')
print(p.read_text(), end='')
`, { output: { capture: 'all' } });

console.log(result.stdout); // EDITED\n
console.log(new TextDecoder().decode(
  await vm.filesystem.readFile('/workspace/data.txt'),
)); // original\n -- unexpected

Expected: both reads return EDITED\n. Actual: Python sees its edit and exits successfully, but the following filesystem API read returns original\n; subsequent executions also see the reverted data. A read-modify-write (p.write_text(p.read_text() + 'added\\n')) has the same problem. Seeding a fresh file through Python rather than through the filesystem API avoided the stale API-created shadow in our tests.

Verification

  • Reproduced with the published 0.2.22 packages on Linux arm64, including embedded AgentOs.create() without HTTP, Rivet actors, or PostgreSQL. That setup used a command-registration workaround to enable Python; this report starts after Python executes successfully.
  • Independently reproduced against unpatched upstream source at c3698e6a60be0a6fed61d411779930e0a7753fc8 using the native-sidecar Rust wire harness and ExecuteRequest { runtime: Python, ... }. This bypasses SDK interpreter-command registration entirely.
  • The new regression fails with left: "original\n", right: "EDITED\n" on its first post-execution host read. The bridge bundle was built from that checkout; no application storage service is needed.

Suspected cause

The wire WriteFile handler mirrors bytes into the VM's root staging/shadow tree. service_owned_python_filesystem_rpc_request handles Python Write by updating only the kernel VFS. Subsequent shadow-to-kernel reconciliation imports the unchanged staging copy and overwrites the newer Python bytes. The JavaScript mirroring change in #1981 does not run on this Python RPC path.

I am preparing a focused fix and regressions for Python writes, including read-modify-write and atomic replacement. Python append-position/truncate behavior and shell/WASM shadow writes are separate from this fix.

Activity

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