world-local write() does not retry a transient Windows EPERM from its existence probe
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 2.4k
- Forks
- 365
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 169
Description
The existence check in write() calls fs.access(filePath) directly. A transient Windows EPERM, EBUSY, or EACCES escapes before the atomic write begins, even though the same module's withWindowsRetry handles these errors for rename/unlink. Current upstream source still has the direct access call in packages/world-local/src/fs.ts (file SHA af18b50b6d0d0315bd976fc33dffc2b0f8fbb0d7).
Measured with Node 24.16.0 on Windows and @workflow/world-local 4.2.4. A single injected EPERM is sufficient; no concurrent writes are needed. The reproducer temporarily intercepts the public Node filesystem API within its own process. It does not edit node_modules or require an actual Defender lock.
Install the pinned package in a disposable project and save the following as repro.mjs:
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createLocalWorld } from "@workflow/world-local";
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "world-access-repro-"));
const world = createLocalWorld({ dataDir, recoverActiveRuns: false });
const access = fs.promises.access;
let probes = 0;
fs.promises.access = async (file, ...args) => {
if (typeof file === "string" && path.dirname(file) === path.join(dataDir, "streams", "chunks")) {
probes++;
if (probes === 1) {
throw Object.assign(new Error("transient Windows file lock"), {
code: "EPERM", syscall: "access", path: file,
});
}
}
return access(file, ...args);
};
try {
await world.writeToStream("example", "run_1", "hello");
console.log({ probes, saved: true });
} catch (error) {
console.log({ probes, saved: false, code: error.code, syscall: error.syscall });
} finally {
fs.promises.access = access;
}
Run node repro.mjs. Observed: { probes: 1, saved: false, code: "EPERM", syscall: "access" }. Expected on Windows: retry the existence probe, let the subsequent ENOENT indicate a missing file, and successfully save one chunk. If the file exists after a retry, overwrite=false must still raise the normal conflict rather than overwriting it. Exhausted permission/lock failures must still reject.
Suggested upstream handling: apply withWindowsRetry to the existence probe while retaining the existing ENOENT/conflict semantics.
Consumer impact: https://github.com/libredb/libredb-studio/issues/900. With STORAGE_PROVIDER=sqlite, WORKFLOW_TARGET_WORLD=local, a writable WORKFLOW_LOCAL_DATA_DIR, and enabled model configuration, our availability probe is green. Injecting one EPERM during ordinary sequential AgentRunService calls can reject start, narrative append, or EOF publication; an index-write fault leaves a terminal run missing from history (our service logs that failure). This proves reachability under fault injection; it does not measure naturally occurring lock frequency or claim a live-model reproduction.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/world-local/src/fs.ts and run the supplied repro.mjs against the pinned package to confirm the direct fs.access failure. Trace the existing withWindowsRetry behavior used by rename and unlink, then verify that a transient probe error retries, missing files still save, existing files still produce the conflict, and exhausted lock or permission errors reject.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 82/100