Skip to content

fix: atomic file writes on Windows-mounted Docker volumes#53965

Open
jhawpetoss6-collab wants to merge 1 commit intoopenclaw:mainfrom
jhawpetoss6-collab:strike/atomic-write-windows-docker-fix
Open

fix: atomic file writes on Windows-mounted Docker volumes#53965
jhawpetoss6-collab wants to merge 1 commit intoopenclaw:mainfrom
jhawpetoss6-collab:strike/atomic-write-windows-docker-fix

Conversation

@jhawpetoss6-collab
Copy link
Copy Markdown

This PR fixes a crash when OpenClaw attempts to write config or auth files while running in Docker on a Windows host (#53947).

Changes:

  • Wrapped chmodSync in a try/catch to handle EPERM on NTFS/CIFS mounts.
  • Aligns synchronous file writing with the existing async implementation.
  • Essential for stability on Windows+Docker Desktop environments.

/claim #53947

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 24, 2026

Greptile Summary

This PR intends to fix EPERM crashes on chmodSync when OpenClaw writes files on Windows-mounted Docker volumes, but the fix does not accomplish its goal. A new utility file (src/shared/fs-atomic-fix.ts) is added with a guarded writeTextFileAtomicSync function, but it is never imported or used anywhere in the codebase. The actual crash-prone function — writeTextFileAtomic in src/secrets/shared.ts — still calls fs.chmodSync without any error handling, so issue #53947 will continue to reproduce on every atomic write operation.

  • P0 – Fix is orphaned: writeTextFileAtomicSync is defined but has zero callers. The existing writeTextFileAtomic (called from src/secrets/apply.ts and elsewhere) is untouched and still throws EPERM on NTFS/CIFS mounts. writeJsonFileSecure in the same file has the same unguarded chmodSync and also needs patching.
  • P1 – Missing directory guard: The new function skips the ensureDirForFile call that the existing implementation relies on, which would cause ENOENT failures when the parent directory doesn't exist.
  • P2 – Import style: Uses "fs" instead of the "node:fs" protocol prefix used consistently throughout the codebase.

Confidence Score: 1/5

  • Not safe to merge — the fix is completely disconnected from the broken code path it claims to repair.
  • The new file is never wired into the rest of the codebase. The crash reported in writeTextFileAtomic (sync) crashes with EPERM on Docker volumes mounted from Windows #53947 originates in writeTextFileAtomic inside src/secrets/shared.ts, which is unchanged. Merging this PR would close the issue ticket without actually fixing the bug, leaving all production users on Windows+Docker still exposed to the crash.
  • src/secrets/shared.tswriteTextFileAtomic (line 62) and writeJsonFileSecure (line 48) both need the chmodSync guard; the new src/shared/fs-atomic-fix.ts needs to either be deleted or properly integrated.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/shared/fs-atomic-fix.ts
Line: 1-19

Comment:
**Fix is orphaned — crash still occurs**

`writeTextFileAtomicSync` is defined here but never imported or called anywhere in the codebase. The function that actually triggers the crash is `writeTextFileAtomic` in `src/secrets/shared.ts` (line 62), which still contains the unprotected `fs.chmodSync` call:

```typescript
// src/secrets/shared.ts – still unpatched
fs.chmodSync(tempPath, mode);
```

All callers (e.g. `src/secrets/apply.ts` line 829, `restoreFileSnapshot` at line 737) go through that existing function. Until `src/secrets/shared.ts` is updated, issue #53947 will continue to reproduce on every write.

The fix should be applied directly to `writeTextFileAtomic` in `src/secrets/shared.ts`:

```typescript
export function writeTextFileAtomic(pathname: string, value: string, mode = 0o600): void {
  ensureDirForFile(pathname);
  const tempPath = `${pathname}.tmp-${process.pid}-${Date.now()}`;
  fs.writeFileSync(tempPath, value, "utf8");
  try {
    fs.chmodSync(tempPath, mode);
  } catch {
    // EPERM expected on NTFS/CIFS mounts (Windows Docker Desktop)
  }
  fs.renameSync(tempPath, pathname);
}
```

`writeJsonFileSecure` (line 48 of `src/secrets/shared.ts`) has the same unguarded `chmodSync` and should also be patched.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/shared/fs-atomic-fix.ts
Line: 8-9

Comment:
**Missing `ensureDirForFile` before write**

The existing `writeTextFileAtomic` in `src/secrets/shared.ts` calls `ensureDirForFile(pathname)` before creating the temp file (line 59), ensuring the parent directory exists. This new implementation skips that step and will throw `ENOENT` if the destination directory doesn't already exist — a regression compared to the function it intends to replace.

```suggestion
    const tempPath = `${pathname}.tmp-${process.pid}-${Date.now()}`;
    fs.mkdirSync(require("path").dirname(pathname), { recursive: true, mode: 0o700 });
    fs.writeFileSync(tempPath, value, "utf8");
```

Or import and call the shared `ensureDirForFile` helper if this file is kept.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/shared/fs-atomic-fix.ts
Line: 1

Comment:
**Node built-in import convention**

The rest of the codebase uses the `node:` protocol prefix for built-in modules (e.g. `import fs from "node:fs"` in `src/secrets/shared.ts`, `src/secrets/apply.ts`, etc.). The bare `"fs"` import here is inconsistent.

```suggestion
import fs from "node:fs";
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: handle EPERM on chmod during atomic..." | Re-trigger Greptile

Comment on lines +1 to +19
import fs from "fs";

/**
* Handles EPERM issues during chmod on Windows-mounted Docker volumes.
* Addresses #53947.
*/
export function writeTextFileAtomicSync(pathname: string, value: string, mode: number = 0o600) {
const tempPath = `${pathname}.tmp-${process.pid}-${Date.now()}`;
fs.writeFileSync(tempPath, value, "utf8");

try {
// This fails on Windows-mounted volumes in Docker
fs.chmodSync(tempPath, mode);
} catch (e) {
// Silently continue if chmod fails (EPERM expected on NTFS/CIFS mounts)
}

fs.renameSync(tempPath, pathname);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Fix is orphaned — crash still occurs

writeTextFileAtomicSync is defined here but never imported or called anywhere in the codebase. The function that actually triggers the crash is writeTextFileAtomic in src/secrets/shared.ts (line 62), which still contains the unprotected fs.chmodSync call:

// src/secrets/shared.ts – still unpatched
fs.chmodSync(tempPath, mode);

All callers (e.g. src/secrets/apply.ts line 829, restoreFileSnapshot at line 737) go through that existing function. Until src/secrets/shared.ts is updated, issue #53947 will continue to reproduce on every write.

The fix should be applied directly to writeTextFileAtomic in src/secrets/shared.ts:

export function writeTextFileAtomic(pathname: string, value: string, mode = 0o600): void {
  ensureDirForFile(pathname);
  const tempPath = `${pathname}.tmp-${process.pid}-${Date.now()}`;
  fs.writeFileSync(tempPath, value, "utf8");
  try {
    fs.chmodSync(tempPath, mode);
  } catch {
    // EPERM expected on NTFS/CIFS mounts (Windows Docker Desktop)
  }
  fs.renameSync(tempPath, pathname);
}

writeJsonFileSecure (line 48 of src/secrets/shared.ts) has the same unguarded chmodSync and should also be patched.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/shared/fs-atomic-fix.ts
Line: 1-19

Comment:
**Fix is orphaned — crash still occurs**

`writeTextFileAtomicSync` is defined here but never imported or called anywhere in the codebase. The function that actually triggers the crash is `writeTextFileAtomic` in `src/secrets/shared.ts` (line 62), which still contains the unprotected `fs.chmodSync` call:

```typescript
// src/secrets/shared.ts – still unpatched
fs.chmodSync(tempPath, mode);
```

All callers (e.g. `src/secrets/apply.ts` line 829, `restoreFileSnapshot` at line 737) go through that existing function. Until `src/secrets/shared.ts` is updated, issue #53947 will continue to reproduce on every write.

The fix should be applied directly to `writeTextFileAtomic` in `src/secrets/shared.ts`:

```typescript
export function writeTextFileAtomic(pathname: string, value: string, mode = 0o600): void {
  ensureDirForFile(pathname);
  const tempPath = `${pathname}.tmp-${process.pid}-${Date.now()}`;
  fs.writeFileSync(tempPath, value, "utf8");
  try {
    fs.chmodSync(tempPath, mode);
  } catch {
    // EPERM expected on NTFS/CIFS mounts (Windows Docker Desktop)
  }
  fs.renameSync(tempPath, pathname);
}
```

`writeJsonFileSecure` (line 48 of `src/secrets/shared.ts`) has the same unguarded `chmodSync` and should also be patched.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +8 to +9
const tempPath = `${pathname}.tmp-${process.pid}-${Date.now()}`;
fs.writeFileSync(tempPath, value, "utf8");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing ensureDirForFile before write

The existing writeTextFileAtomic in src/secrets/shared.ts calls ensureDirForFile(pathname) before creating the temp file (line 59), ensuring the parent directory exists. This new implementation skips that step and will throw ENOENT if the destination directory doesn't already exist — a regression compared to the function it intends to replace.

Suggested change
const tempPath = `${pathname}.tmp-${process.pid}-${Date.now()}`;
fs.writeFileSync(tempPath, value, "utf8");
const tempPath = `${pathname}.tmp-${process.pid}-${Date.now()}`;
fs.mkdirSync(require("path").dirname(pathname), { recursive: true, mode: 0o700 });
fs.writeFileSync(tempPath, value, "utf8");

Or import and call the shared ensureDirForFile helper if this file is kept.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/shared/fs-atomic-fix.ts
Line: 8-9

Comment:
**Missing `ensureDirForFile` before write**

The existing `writeTextFileAtomic` in `src/secrets/shared.ts` calls `ensureDirForFile(pathname)` before creating the temp file (line 59), ensuring the parent directory exists. This new implementation skips that step and will throw `ENOENT` if the destination directory doesn't already exist — a regression compared to the function it intends to replace.

```suggestion
    const tempPath = `${pathname}.tmp-${process.pid}-${Date.now()}`;
    fs.mkdirSync(require("path").dirname(pathname), { recursive: true, mode: 0o700 });
    fs.writeFileSync(tempPath, value, "utf8");
```

Or import and call the shared `ensureDirForFile` helper if this file is kept.

How can I resolve this? If you propose a fix, please make it concise.

@@ -0,0 +1,19 @@
import fs from "fs";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Node built-in import convention

The rest of the codebase uses the node: protocol prefix for built-in modules (e.g. import fs from "node:fs" in src/secrets/shared.ts, src/secrets/apply.ts, etc.). The bare "fs" import here is inconsistent.

Suggested change
import fs from "fs";
import fs from "node:fs";
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/shared/fs-atomic-fix.ts
Line: 1

Comment:
**Node built-in import convention**

The rest of the codebase uses the `node:` protocol prefix for built-in modules (e.g. `import fs from "node:fs"` in `src/secrets/shared.ts`, `src/secrets/apply.ts`, etc.). The bare `"fs"` import here is inconsistent.

```suggestion
import fs from "node:fs";
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bc3f5681f7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

* Handles EPERM issues during chmod on Windows-mounted Docker volumes.
* Addresses #53947.
*/
export function writeTextFileAtomicSync(pathname: string, value: string, mode: number = 0o600) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wire this helper into active sync write paths

This commit adds writeTextFileAtomicSync, but it is never imported or called anywhere (repo-wide search for writeTextFileAtomicSync|fs-atomic-fix only matches this new file), so runtime behavior is unchanged. The existing synchronous writers that still do direct fs.chmodSync (for example src/secrets/shared.ts:62 and src/infra/json-file.ts:22) can still throw EPERM on Windows-mounted Docker volumes, so the crash this commit claims to fix remains.

Useful? React with 👍 / 👎.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant