[BUG] Cowork silently corrupts files in OneDrive folders with Files-On-Demand

Status Closed — not planned
Maintainer reply None cached
Activity 7 comments · opened May 25, 2026 · closed Aug 28, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

<html>
<body>
<!--StartFragment--><h2 class="text-text-100 mt-3 -mb-1 text-[1.375rem] font-bold">Bug report: Cowork silently corrupts files in OneDrive folders with Files-On-Demand</h2>
<h3 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Summary</h3>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">When a Cowork session has access to a folder that lives in a OneDrive (or any cloud-sync) directory with Files-On-Demand enabled, Cowork's file tools (<code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">Read</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">Edit</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">Write</code>, and the Linux mount that powers <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">bash</code>) operate on the local <strong>placeholder/stub</strong> of a file rather than the actual cloud-hydrated content. When Cowork then writes to the file (thinking it has the canonical version), it silently overwrites the user's full cloud copy with the truncated stub plus whatever edits Cowork made. <strong>The user loses data silently.</strong> No error, no warning, no diff prompt.</p>
<h3 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Reproduction</h3>
<ol class="[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-decimal flex flex-col gap-1 pl-8 mb-3">
<li class="font-claude-response-body whitespace-normal break-words pl-2">Create a file in a OneDrive folder (e.g., <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">C:\Users\me\OneDrive\Documents\Projects\big-file.md</code>) that is &gt;100 KB and has been synced to cloud but evicted from local cache (the "cloud-only" green-circle icon, or any Files-On-Demand state where the file isn't fully kept on disk).</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">Connect that folder to Cowork.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">Have Cowork read or edit the file. From Cowork's perspective, the file appears to be ~130 KB (the typical placeholder size, regardless of true content size). All sections past that byte boundary appear truncated/missing.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">Have Cowork write to the file (e.g., via <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">Edit</code>, <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">Write</code>, or <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">cp</code> over the mount).</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Result:</strong> the actual file on disk is now the truncated content Cowork saw, plus Cowork's edits. The user's full canonical content is destroyed unless OneDrive's version history can be used to roll back.</li>
</ol>
<h3 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Evidence from this session</h3>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">Files destroyed or corrupted by this pattern in one session:</p>
<div class="overflow-x-auto w-full px-2 mb-6">
File | Real size (user view) | What Cowork saw | What got written back
-- | -- | -- | --
04-decisions.md | ~158 KB (51 ADRs) | ~114 KB (truncated mid-ADR-040, ADRs 041–045 missing) | If we'd written, would have lost ADRs 041–045. Recovered by reconstructing the lost ADRs from cross-references in other docs.
14-ui-mockups.html | ~258 KB (15 sections) | ~24 KB (only sections 1 + start of 2) | Cowork "rebuilt" sections 3–15 with a different (worse) design language. User had to restore from OneDrive version history.
10-runtime-design.md | ~194 KB (full §1–§10) | ~137 KB (truncated mid-§5.13, missing §5.14–§5.19 + §6–§10) | Multiple times. Eventually resolved by user uploading canonical via chat.
Multiple SVG diagrams | ~30 KB | ~30 KB but truncated mid-tag | Render failures (cairosvg couldn't parse); each required patch-via-/tmp workaround.

</div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">The unifying signal that distinguishes a real file from a Files-On-Demand placeholder:</p>
<div role="group" aria-label="bash code" tabindex="0" class="relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100"><div class="sticky opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 top-2 py-2 h-12 w-0 float-right"><div class="absolute right-0 h-8 px-2 items-center inline-flex z-10"><button class="inline-flex
items-center
justify-center
relative
isolate
shrink-0
can-focus
select-none
disabled:pointer-events-none
disabled:opacity-50
disabled:shadow-none
disabled:drop-shadow-none border-transparent
transition
font-base
duration-300
ease-[cubic-bezier(0.165,0.85,0.45,1)] h-8 w-8 rounded-md backdrop-blur-md _fill_10ocf_9 _ghost_10ocf_96" type="button" aria-label="Copy to clipboard" data-state="closed"><div class="relative"><div class="transition-all opacity-100 scale-100" style="width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="transition-all opacity-100 scale-100" aria-hidden="true" style="flex-shrink: 0;"><path d="M12.5 3A1.5 1.5 0 0 1 14 4.5V6h1.5A1.5 1.5 0 0 1 17 7.5v8a1.5 1.5 0 0 1-1.5 1.5h-8A1.5 1.5 0 0 1 6 15.5V14H4.5A1.5 1.5 0 0 1 3 12.5v-8A1.5 1.5 0 0 1 4.5 3zm1.5 9.5a1.5 1.5 0 0 1-1.5 1.5H7v1.5a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-8a.5.5 0 0 0-.5-.5H14zM4.5 4a.5.5 0 0 0-.5.5v8a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-8a.5.5 0 0 0-.5-.5z"></path></svg></div><div class="absolute inset-0 flex items-center justify-center"><div class="transition-all opacity-0 scale-50" style="width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="transition-all opacity-0 scale-50" aria-hidden="true" style="flex-shrink: 0;"><path d="M15.188 5.11a.5.5 0 0 1 .752.626l-.056.084-7.5 9a.5.5 0 0 1-.738.033l-3.5-3.5-.064-.078a.501.501 0 0 1 .693-.693l.078.064 3.113 3.113 7.15-8.58z"></path></svg></div></div></div></button></div></div><div class="text-text-500 font-small p-3.5 pb-0">bash</div><div class="overflow-x-auto"><pre class="code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed p-3.5" style="color: rgb(20, 24, 31); background: transparent; font-family: var(--font-mono);"><code class="language-bash" style="color: rgb(20, 24, 31); background: transparent; font-family: var(--font-mono); white-space: pre;"><span><span>$ </span><span class="token token" style="color: rgb(0, 81, 194);">stat</span><span> </span><span class="token token" style="color: rgb(0, 128, 128);">10</span><span>-runtime-design.md
</span></span><span><span> Size: </span><span class="token token" style="color: rgb(0, 128, 128);">137377</span><span> Blocks: </span><span class="token token" style="color: rgb(0, 128, 128);">0</span><span> IO Block: </span><span class="token token" style="color: rgb(0, 128, 128);">262144</span><span> regular </span><span class="token token" style="color: rgb(0, 81, 194);">file</span><span>
</span></span><span> ^^^^^^^^^
</span><span> ZERO BLOCKS on disk despite non-zero Size</span></code></pre></div></div>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">Blocks: 0</code> (or any value much smaller than <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">Size / 4096</code>) is the giveaway. The Windows attribute is <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">FILE_ATTRIBUTE_RECALL_ON_OPEN</code> / <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS</code>.</p>
<h3 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Suggested mitigations (in order of impact)</h3>
<ol class="[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-decimal flex flex-col gap-1 pl-8 mb-3">
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Detect Files-On-Demand placeholders before reading.</strong> On every file Read from a OneDrive/iCloud/Google Drive folder, check <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">stat</code> for <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">Blocks == 0 &amp;&amp; Size &gt; 0</code>. If detected, either (a) force hydration by reading via a Windows API call that resolves the placeholder (the standard Win32 <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">CreateFile</code>/<code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">ReadFile</code> does this automatically when called from a non-FILE_FLAG_OPEN_NO_RECALL context), or (b) error out and surface to the user: "This file is currently cloud-only. Click to download for editing, then retry."</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Refuse to write to a file that looks dramatically smaller than the version Cowork last saw.</strong> A simple guard: if a write is about to replace a file whose previous size was &gt;2× larger, require an explicit confirmation or skip. This single check would have prevented every data-loss incident in this session.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Warn prominently in the docs / agent system prompt.</strong> Even just adding to the system prompt "Cloud-sync folders (OneDrive, iCloud, Google Drive) with Files-On-Demand can serve truncated placeholder files; verify with <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">stat ... Blocks</code> before treating large files as canonical" would let agents detect and route around the problem.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>Use the OneDrive / iCloud / Google Drive API directly when available.</strong> For OneDrive folders, Microsoft Graph API can read the cloud copy regardless of local state. Cowork could detect a OneDrive path and use Graph instead of the local mount for files &gt;N KB.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2"><strong>For the Linux mount specifically:</strong> force-hydrate every file in the connected folder on session start, or document clearly that the mount is "best-effort over Files-On-Demand" and provide a fallback path.</li>
</ol>
<h3 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Workaround we used (which should not be the user's job)</h3>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">For every doc edit in this session past the first incident:</p>
<ol class="[li_&amp;]:mb-0 [li_&amp;]:mt-1 [li_&amp;]:gap-1 [&amp;:not(:last-child)_ul]:pb-1 [&amp;:not(:last-child)_ol]:pb-1 list-decimal flex flex-col gap-1 pl-8 mb-3">
<li class="font-claude-response-body whitespace-normal break-words pl-2">User drags the file into chat (uploads to Cowork's <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">/uploads/</code> folder, which is in <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">AppData</code> — outside OneDrive, no Files-On-Demand layer).</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">Cowork reads from <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">/uploads/</code>, edits a copy in <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">/outputs/</code> (also <code class="bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]">AppData</code>).</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">User manually opens both files in their editor, copies the patched version, pastes over the OneDrive original.</li>
<li class="font-claude-response-body whitespace-normal break-words pl-2">Cowork verifies indirectly (file size in Windows Explorer matches Cowork's patched-file size).</li>
</ol>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]">This is six manual steps per edit on what should be a single-tool-call operation. The user effectively had to operate two file systems and broker between them. They (rightly) called this out as a Cowork-team-should-fix-it problem.</p>
<h3 class="text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold">Severity</h3>
<p class="font-claude-response-body break-words whitespace-normal leading-[1.7]"><strong>High.</strong> The default Windows + OneDrive configuration (Files-On-Demand on, the default since Windows 10 1709) is the trigger. Anyone using Cowork against a OneDrive project folder is at risk of silent data loss on any large file the agent edits. The user has no way to know it happened until they notice the missing content later.</p><!--EndFragment-->
</body>
</html>

What Should Happen?

Files should sync from onedrive to shared drive without issue.

Error Messages/Logs

Steps to Reproduce

see original message above

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

Claude 1.8555.2 (a476c3) 2026-05-22T23:04:37.000Z

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Other

Additional Information

_No response_

View original on GitHub ↗

6 Comments

github-actions[bot] · 3 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/55627
  2. https://github.com/anthropics/claude-code/issues/38993
  3. https://github.com/anthropics/claude-code/issues/50886

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

tlrichar · 3 months ago

These don't seem to be related to the same problem I'm seeing. #2 looks somewhat similar, but this is definitely related to OneDrive, as we can copy the files to an unwatched directory and not have this problem.

jshaofa-ui · 3 months ago

Proposed Solution

Solution: claude-code #62140 — Cowork OneDrive Files-On-Demand Silent Data Corruption

Issue Summary

  • Repo: anthropics/claude-code
  • Issue: #62140
  • Title: Cowork silently corrupts files in OneDrive folders with Files-On-Demand
  • Labels: bug, data-loss, platform:windows, area:tools, area:cowork
  • Severity: P0 — Silent data loss, no warning, no diff prompt
  • Competition: Zero (only bot duplicate-detection + author response)
  • Price: $3,000–$5,000

---

Root Cause Analysis

The Files-On-Demand Problem

Windows OneDrive/iCloud/Google Drive with Files-On-Demand enabled stores "placeholder stub" files on disk. These files:

  • Report a non-zero Size in stat (the cloud file size)
  • Report Blocks: 0 (no actual data blocks allocated on disk)
  • Have the FILE_ATTRIBUTE_RECALL_ON_OPEN / FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS Windows attributes
  • When read through the Linux mount layer (WSL2 drvfs, 9P), the placeholder content is returned instead of triggering hydration

The Data Loss Chain

  1. User has a project folder in OneDrive with Files-On-Demand (default on Windows 10+)
  2. Cowork mounts the folder via Linux mount (WSL2 drvfs or similar)
  3. Cowork's file tools (Read, Edit, Write) operate on the mount
  4. When reading a cloud-only file, the mount returns the ~130KB placeholder stub, not the full file
  5. Cowork edits the stub content, believing it has the canonical version
  6. Cowork writes back the edited stub, destroying the full cloud copy
  7. No error, no warning, no diff prompt — silent data loss

Evidence from the Issue

| File | Real size | What Cowork saw | What got written |
|------|-----------|-----------------|-----------------|
| 04-decisions.md | ~158 KB (51 ADRs) | ~114 KB (truncated mid-ADR-040) | Would have lost ADRs 041–045 |
| 14-ui-mockups.html | ~258 KB (15 sections) | ~24 KB (sections 1 + start of 2) | Cowork "rebuilt" sections 3–15 with worse design |
| 10-runtime-design.md | ~194 KB (full §1–§10) | ~137 KB (truncated mid-§5.13) | Multiple times |
| Multiple SVG diagrams | ~30 KB | ~30 KB but truncated mid-tag | Render failures |

Detection Signal

$ stat 10-runtime-design.md
  Size: 137377   Blocks: 0       IO Block: 262144 regular file
                        ^^^^^^^^^
                        ZERO BLOCKS on disk despite non-zero Size

Blocks == 0 && Size > 0 is the definitive signal of a Files-On-Demand placeholder.

---

Fix Approach (5-Layer Defense)

Layer 1: Pre-Read Placeholder Detection (Critical)

File: src/tools/file/read.ts (or equivalent file read tool)

Before every file read operation, detect placeholders and force hydration:

import { statSync, readFileSync } from 'fs';
import { platform } from 'os';
import { execSync } from 'child_process';

interface FileReadResult {
  content: string;
  warning?: string;
  hydrated?: boolean;
}

function isPlaceholderFile(filePath: string): boolean {
  if (platform() !== 'win32') return false;
  
  try {
    const st = statSync(filePath);
    // Placeholder: non-zero size but zero allocated blocks
    if (st.size > 0 && (st as any).blocks === 0) {
      return true;
    }
    // Also check Windows file attributes via wslvar
    const wslPath = windowsToWslPath(filePath);
    const wslStat = execSync(`stat -c "%b" "${wslPath}" 2>/dev/null`).toString().trim();
    const wslSize = execSync(`stat -c "%s" "${wslPath}" 2>/dev/null`).toString().trim();
    if (parseInt(wslSize) > 0 && parseInt(wslStat) === 0) {
      return true;
    }
  } catch {
    // File doesn't exist or can't be stat'd — let the read fail naturally
  }
  return false;
}

function forceHydrateFile(filePath: string): void {
  // Method 1: Use PowerShell to trigger OneDrive hydration
  if (platform() === 'win32') {
    try {
      execSync(
        `powershell -Command "Add-Type -Name FileUtil -MemberDefinition '[DllImport(\"kernel32.dll\")] public static extern bool SetFileAttributes(string lpFileName, uint dwFileAttributes);' -PassThru; [FileUtil]::SetFileAttributes('${filePath}', 0x00000004)"`,
        { timeout: 10000 }
      );
    } catch {
      // Fallback: use fsync to trigger recall
    }
  }
  
  // Method 2: Open with CreateFile (no FILE_FLAG_OPEN_NO_RECALL)
  // This is the default behavior — just reading the file triggers hydration
  // The issue is the Linux mount layer bypassing this
  const fd = openSync(filePath, constants.O_RDONLY);
  // Read at least 1 byte to trigger the recall
  const buf = Buffer.alloc(1);
  readSync(fd, buf, 0, 1, 0);
  closeSync(fd);
}

function readFileWithPlaceholderGuard(filePath: string): FileReadResult {
  if (isPlaceholderFile(filePath)) {
    forceHydrateFile(filePath);
    // Verify hydration succeeded
    if (isPlaceholderFile(filePath)) {
      return {
        content: '',
        warning: `File "${filePath}" is a cloud-only placeholder and could not be hydrated. ` +
          `Please ensure the file is downloaded locally before editing, or copy it to a non-synced directory.`,
        hydrated: false
      };
    }
    return {
      content: readFileSync(filePath, 'utf-8'),
      warning: `File "${filePath}" was a cloud-only placeholder and has been hydrated.`,
      hydrated: true
    };
  }
  return { content: readFileSync(filePath, 'utf-8') };
}

Layer 2: Write Size Guard (Data Loss Prevention)

File: src/tools/file/write.ts (or equivalent file write tool)

Prevent writes that would dramatically shrink a file:

const SIZE_RATIO_THRESHOLD = 2.0; // Warn if new content is <50% of original size
const MAX_SILENT_SHRINK = 1024;   // Bytes — allow small truncations (trailing newline etc.)

interface WriteGuardResult {
  allowed: boolean;
  reason?: string;
  originalSize: number;
  newSize: number;
}

function checkWriteGuard(
  filePath: string,
  newContent: string,
  previousKnownSize?: number
): WriteGuardResult {
  try {
    const st = statSync(filePath);
    const originalSize = previousKnownSize ?? st.size;
    const newSize = Buffer.byteLength(newContent, 'utf-8');
    
    // Allow if file is small or shrink is minimal
    if (originalSize < MAX_SILENT_SHRINK) {
      return { allowed: true, originalSize, newSize };
    }
    
    const ratio = newSize / originalSize;
    if (ratio < 1 / SIZE_RATIO_THRESHOLD) {
      return {
        allowed: false,
        reason: `Write would reduce file from ${originalSize} bytes to ${newSize} bytes ` +
          `(${Math.round(ratio * 100)}% of original). This may indicate a truncated read ` +
          `(e.g., cloud-sync placeholder). Please verify the file content is complete ` +
          `before writing, or confirm this truncation is intentional.`,
        originalSize,
        newSize
      };
    }
    return { allowed: true, originalSize, newSize };
  } catch {
    // File doesn't exist — this is a new file, allow
    return { allowed: true, originalSize: 0, newSize: Buffer.byteLength(newContent, 'utf-8') };
  }
}

Layer 3: System Prompt Warning

Add to the Cowork system prompt:

IMPORTANT: When working with files in cloud-synced folders (OneDrive, iCloud, Google Drive),
the file may be a "placeholder" that appears smaller than the actual cloud content.
Before reading or editing any file >10KB in a synced folder:
1. Run `stat <file>` and check that "Blocks" is non-zero (or Size/Blocks ratio is reasonable)
2. If Blocks==0 and Size>0, the file is cloud-only — DO NOT edit it directly
3. Instead, copy it to a local directory first, edit there, then copy back
4. Or force hydration: on Windows, opening the file in Explorer or running `type <file>` in PowerShell

Layer 4: Session-Level Placeholder Scan

On Cowork session start, scan the connected folder for placeholders:

async function scanForPlaceholders(rootDir: string): Promise<string[]> {
  const placeholders: string[] = [];
  
  async function scan(dir: string) {
    const entries = await readdir(dir, { withFileTypes: true });
    for (const entry of entries) {
      const fullPath = join(dir, entry.name);
      if (entry.isDirectory()) {
        // Skip .git, node_modules, etc.
        if (['.git', 'node_modules', '.DS_Store'].includes(entry.name)) continue;
        await scan(fullPath);
      } else if (entry.isFile()) {
        try {
          const st = statSync(fullPath);
          if (st.size > 1024 && (st as any).blocks === 0) {
            placeholders.push(fullPath);
          }
        } catch {
          // Skip inaccessible files
        }
      }
    }
  }
  
  await scan(rootDir);
  return placeholders;
}

Layer 5: Linux Mount Force-Hydration

For the Linux mount layer (WSL2 drvfs), force-hydrate all files in the connected folder on session start:

# In the mount setup script, before exposing files to Cowork:
# Force hydration of all OneDrive/iCloud placeholders
find "$MOUNT_ROOT" -type f -size +1k -exec sh -c '
  blocks=$(stat -c "%b" "$1" 2>/dev/null)
  size=$(stat -c "%s" "$1" 2>/dev/null)
  if [ "$size" -gt 0 ] && [ "$blocks" -eq 0 ]; then
    # Trigger hydration by reading first byte
    dd if="$1" of=/dev/null bs=1 count=1 2>/dev/null
  fi
' _ {} \;

---

Testing Plan

Unit Tests

  1. Placeholder detection: Mock stat to return blocks=0, size=137377 — verify isPlaceholderFile() returns true
  2. Hydration trigger: Verify forceHydrateFile() changes blocks from 0 to non-zero
  3. Write guard: Test with original=194KB, new=137KB — verify rejection
  4. Write guard edge case: Test with original=100 bytes, new=50 bytes — verify allowance (small file)

Integration Tests

  1. OneDrive folder simulation: Create a file with blocks=0 using truncate + chattr on Linux
  2. Full read-edit-write cycle: Verify placeholder detection → hydration → correct edit → safe write
  3. Cross-platform: Test on Windows + WSL2, macOS + iCloud, Linux + Google Drive

E2E Tests

  1. Cowork session with OneDrive folder: Connect Cowork to a OneDrive folder with cloud-only files
  2. Verify no data loss: After Cowork edits, compare file content with cloud version
  3. Stress test: 50 files of varying sizes in OneDrive, Cowork edits all — verify zero corruption

---

Impact Assessment

  • User impact: HIGH — Any Cowork user with OneDrive/iCloud/Google Drive synced folders is at risk
  • Data loss severity: CRITICAL — Silent, no warning, no recovery without version history
  • Fix complexity: MODERATE — 5-layer approach, each layer is independently deployable
  • Risk of fix: LOW — Detection is read-only, write guard is conservative, hydration is standard Windows API
  • Rollout strategy:
  1. Deploy Layer 2 (write guard) immediately — zero risk, prevents all data loss
  2. Deploy Layer 1 (placeholder detection) — may slow first read of cloud files
  3. Deploy Layer 3 (system prompt) — immediate mitigation
  4. Deploy Layer 4+5 (scan + mount hydration) — session startup optimization

---

Pricing: $3,000–$5,000

  • P0 data-loss bug with confirmed reproduction
  • Detailed evidence table with real file corruption data
  • Zero competition
  • Multi-layer fix with code-level implementations
  • Comprehensive testing plan
  • Immediate deployability (write guard can ship independently)
mauromelani · 2 months ago

Summary

On Cowork (Claude Desktop, Windows), after a file under a OneDrive-synced folder is
edited via the file tools (Read/Write/Edit, which operate on the real Windows disk),
the SAME file read from the Linux sandbox bash (via the mounted folder under
/sessions/<id>/mnt/...) is served TRUNCATED — cut mid-line, fewer lines than the
real file. The authoritative file on disk is complete. No error is raised.

This looks related to the existing OneDrive Files-On-Demand reports (#62140, #41702,
#50873, #41710, #25293), but with a key difference that narrows the root cause:
the affected files are fully hydrated AND pinned ("Always keep on this device"),
NOT dehydrated cloud stubs.
So the popular "Cowork reads the dehydrated placeholder
stub" explanation does not apply here. The data is fully present locally; the mount
still serves a short view.

Environment

  • OS: Windows 11 Home, build 10.0.26200.8655
  • OneDrive client: 26.095.0519.0003
  • OneDrive Files On-Demand: enabled (this version has no GUI toggle to disable it)
  • Affected folders: working folders under C:\Users\<user>\OneDrive\...

Evidence (verified from Windows side)

For an affected .py file:

  • Real size on disk: 205337 bytes / 4262 lines (confirmed via PowerShell Get-Content).
  • File attributes: 0x80420 = ARCHIVE + REPARSE_POINT + PINNED.
  • PINNED (0x80000) = "Always keep on this device".
  • NO RECALL_ON_DATA_ACCESS (0x400000), NO RECALL_ON_OPEN (0x200000),

NO OFFLINE (0x1000) => file is fully materialized locally, not a stub.

  • Reparse tag: 0x9000601a (IO_REPARSE_TAG_CLOUD — OneDrive cldflt). Present even

though the file is pinned and fully downloaded.

  • Read via the file tools: always returns the full, correct content.
  • Read via the Linux sandbox bash (mounted path): returns truncated content,

cut at end-of-file; python -m py_compile fails with SyntaxError at the cut point.

Observed characteristics

  • Truncation is always at the END of the file (clipped, not corrupt in the middle).
  • It persists for the whole session; waiting 20-40s does not fix it.
  • A full Windows reboot temporarily restores the complete view via the mount, but

subsequent edits (from the Windows file tools) re-truncate it.

  • Large BINARY files (.xlsx) COPIED BY the bash itself show the correct full size

=> not a total mount failure; specific to text files modified EXTERNALLY
(Windows side) and then read through the mount.

Hypothesized root cause

Cache-coherence failure in the sandbox host-folder mount (virtiofs/Plan9) for
OneDrive cloud-reparse-point files that are modified by an external (host-side)
writer. The mount appears to cache file metadata (size) and/or content pages from
before the external edit and does not invalidate on external modification. OneDrive's
cloud filter (cldflt) reparse indirection seems to be the trigger: files copied by
the guest itself are fine; plain external writes to cloud-reparse files are not.
Because pinned+hydrated files still carry the cloud reparse tag, pinning does NOT
avoid the bug.

Repro

  1. On Windows, place a multi-thousand-line text file in a OneDrive folder set to

"Always keep on this device".

  1. In Cowork, edit/grow it with the file tools (Write/Edit).
  2. From the Linux sandbox bash, wc -l / cat the same file via the mounted path.

=> Observed: fewer lines than the real file; content cut mid-line at the end.
python -m py_compile <file> => SyntaxError at the cut point.

  1. Read the same file with the file tools => full, correct content.

Impact / data-loss risk

Read truncation is silent (no error). The dangerous corollary: if the sandbox writes
the file back based on the truncated view, it overwrites the complete file with a
truncated one. For users with default Windows + OneDrive (Files On-Demand on by
default), this is a real data-loss vector. See #62140 (labeled data-loss).

Suggested fixes

  • Invalidate the mount's attribute/page cache on external modification of

cloud-reparse files (or disable caching for reparse-point-backed files).

  • At minimum, detect a size/Blocks mismatch between the mount view and the host

file and surface an error instead of silently serving a short read.

  • Never let a write proceed when the about-to-be-written file is dramatically

smaller than the last-seen size, for cloud-sync folders.

Related issues

#62140, #41702, #50873, #41710, #25293

JBinAussie · 2 months ago

A related but distinct failure mode: binary writes to OneDrive-on-external-drive are blocked outright, not just truncated

Landing here after #19318 was closed as a duplicate of #24964 (also closed) — that one covered the Windows "home directory only" restriction, but my case is macOS + external drive, so it doesn't quite fit either thread. This issue is the closest match, but worth flagging that what I'm hitting is a step earlier than the corruption described above: the file never gets far enough to be truncated, because it can't be written at all.

Setup

  • MacBook (Apple Silicon), current macOS
  • Claude Desktop, Cowork research preview
  • Target folder: OneDrive-synced folder on an external SSD

Two bugs stacked together

  1. The bash sandbox never mounts the external volume at all. cp inside bash, re-requesting the folder, quitting/relaunching, rebooting — all fail with "couldn't be mounted into the bash sandbox... No such file or directory." Checking the sandbox's mount table confirms only internal app folders (outputs, uploads, .auto-memory, .claude, .remote-plugins) are FUSE-bridged; the external volume is absent regardless of /Volumes/ permissions or Full Disk Access grants. This matches the "intended behaviour, looking at how to support external drives" note from @MarshallOfSound on #24964 — so it sounds like this is a known, accepted gap rather than something specific to me, just not yet fixed for macOS external volumes either.
  1. **Even once the host file tools reach the folder (by connecting the directory, which grants host-side access separately from the sandbox mount), Write is text-only.** A plain-text test file writes fine — proving the folder itself is reachable — but there's no binary/base64 mode, so a generated DOCX/PDF (built inside the sandbox, since that's where the doc-generation tooling actually runs) can never be copied out to the folder. Read refuses the binary in the reverse direction too.

Net effect: binary output to this folder is blocked from both directions, with no truncation or corruption involved — it simply never lands. I'd guess the Files-On-Demand truncation described above kicks in for whoever gets past step 1 (i.e. a folder that does get mounted, or is accessed purely through the host file tools); on external+cloud-sync macOS setups you don't even get that far.

Why the usual workarounds don't apply

  • Symlink into home directory → resolved and rejected by Cowork's home-directory check (same underlying restriction as #24964).
  • SMB/NFS loopback mount → reported to fail on Apple Silicon + Sonoma+, since macOS sandboxes smbd/nfsd away from /Volumes/.
  • Google Drive works fine for the same binary files, but only because it's a separate cloud API connector with base64 upload — nothing to do with the local mount. No equivalent write-capable connector exists for OneDrive/SharePoint (the installed Microsoft 365 connector is search+read only).

Suggested fixes

  1. Extend the sandbox mount to include external volumes on macOS (tracked implicitly by #24964/#19318) — fixes the root cause for this case.
  2. Independent of (1): give the host Write tool a binary/base64 mode, so binaries can reach any host path the file tools already have access to. Also helps anyone who wants Cowork to drop a binary directly instead of building in outputs and dragging it over manually.
  3. A write-capable Microsoft Graph connector (PUT /drive/items/{id}/content) alongside the existing read-only Microsoft 365 connector would mirror the Google Drive path and route around the mount issue for OneDrive/SharePoint specifically.

Current workaround: build the file in Cowork's own outputs folder, then manually drag it into the OneDrive folder for sync.

rstribrn · 1 month ago

**Corroboration + scope expansion (reads and writes; not OneDrive-specific).**

We hit this in an agentic workflow on Windows where the same files are touched both by native file tools (Win32 CreateFile/ReadFile) and by the Linux sandbox host-share mount.

Silent write truncation (data loss, no error): an in-place bash write on a partially-materialized mount view (sed -i, and shell redirection >/>>) rewrote and cut off the tail of several already-complete files. No error was raised; the Windows-side copies had been correct until the write.

Scope is broader than OneDrive Files-On-Demand: we reproduced the same truncation on a plain local NTFS path (C:\, outside any cloud-sync layer), with stat reporting Blocks != 0 at nonzero Size — i.e. not a dehydrated cloud placeholder. So while the Blocks == 0 && Size > 0 placeholder signal is a useful detector for the cloud-only variant, there is a second variant that is a host-share (virtiofs/Plan9) coherence problem affecting arbitrary host files (overlaps #50873/#41710).

Workaround that holds for us: never do in-place bash writes on the mount — write via native file tools, and for moves use a mv rename (never cp of a possibly-stale mount view). To refresh a stale mount view, a mv rename round-trip (mv x y && mv y x) reliably restores the correct content (verified byte-identical: md5 before == after). Prefer write-new-then-rename-over the canonical path (never remove-then-write).

Ask: checksum/length validation on the mount write path (reject a write that shrinks a file well below its last-seen size), and/or force-coherence on access — since the placeholder-hydration fix alone does not cover the NTFS / inode-stale variant.

Showing cached comments. Read the full discussion on GitHub ↗