●SANDBOX — Claude Managed Agents can now run in your own sandbox and connect to private MCP servers (self-hosted beta, MCP tunnels in preview)●PLATFORM — The Claude Developer Platform adds new code execution, web search, and web fetch tools, exposing a 90-second per-cell limit●CONTEXT — response_inclusion trims consumed result blocks to save context in agentic workflows●MCP — Enterprise-managed MCP connectors (Okta) continue: zero-touch access across Claude, Claude Code, and Cowork (Team/Enterprise beta)●CODE — Claude Code adds /cd, a post-session hook, and a safe mode while tightening MCP policy enforcement●MODEL — Opus 4.8, Sonnet 4.6, and Haiku 4.5 lead the lineup; Fable 5 is available from Claude Code●SANDBOX — Claude Managed Agents can now run in your own sandbox and connect to private MCP servers (self-hosted beta, MCP tunnels in preview)●PLATFORM — The Claude Developer Platform adds new code execution, web search, and web fetch tools, exposing a 90-second per-cell limit●CONTEXT — response_inclusion trims consumed result blocks to save context in agentic workflows●MCP — Enterprise-managed MCP connectors (Okta) continue: zero-touch access across Claude, Claude Code, and Cowork (Team/Enterprise beta)●CODE — Claude Code adds /cd, a post-session hook, and a safe mode while tightening MCP policy enforcement●MODEL — Opus 4.8, Sonnet 4.6, and Haiku 4.5 lead the lineup; Fable 5 is available from Claude Code
Why Cowork's bash Says 'No Such File' When Finder Shows It Right There
Connect a cloud-synced folder to Cowork and bash sees empty placeholders while cat fails. Here is how on-demand materialization actually works, and the design patterns that keep your automations from silently dropping data.
Have you ever hit this contradiction in a Cowork automation? The scheduled run logs cat: settings.json: No such file or directory, yet when you open the very same file in Finder, the contents are right there. You connected the folder, but only bash insists the file is missing. This mismatch is not a glitch — it is the mechanics of a cloud-synced folder surfacing directly.
As an indie developer, once I started handing my personal automation over to Cowork scheduled tasks, I tripped over this "visible but unreadable" behavior more than once. The fix is simple once you understand the cause, but if you don't, it shows up in the worst possible form: a task that fails silently. This article takes the problem apart and turns it into design patterns that keep automation from dropping data.
Cloud sync leaves "files with no body"
Dropbox, iCloud Drive, OneDrive and the like use on-demand materialization to save disk space. The directory listing shows every file as if it were present, but the body — the actual bytes — often lives only in the cloud, with just a metadata placeholder sitting locally.
This is where Cowork's structure matters. Cowork's bash is a sandboxed Linux environment that reaches your connected folder through a mount. What bash sees is only "bytes that physically exist on disk." A placeholder shows up in a directory listing (ls), but the moment you cat it, there is no body, so it fails. The Cowork file tools (such as Read), by contrast, trigger a download from the cloud as a side effect of the read request. So for the same path there is an asymmetry: bash cannot request the body, while the file tool can.
That asymmetry is the whole story behind "Finder shows it but bash can't." Finder (and the sync client) kick off a download the instant a placeholder is opened; bash's cat has no such trigger.
Split the symptom into three states
Instead of lumping everything under "can't read it," observe the body's state in three buckets first — the right fix follows directly.
F="/sessions/xxx/mnt/Workspace/settings.json"# 1. Does it appear in the listing? (metadata present?)ls -la "$F" # appears = placeholder exists# 2. Does the body have bytes?stat -c '%s bytes' "$F" 2>/dev/null || echo "stat failed"# 3. Can you actually read it?head -c 16 "$F" 2>&1 | head -1
You will typically see one of these three patterns.
State
ls
stat size
cat / head
Meaning
No body
shows
fails or 0
No such file / I/O error
Undownloaded placeholder
Zero bytes
shows
0
returns empty
Mid-sync / failed materialization
Stale body
shows
positive
reads, but old content
Another device's edit not pulled yet
The third one — a stale body — is the scariest. cat succeeds, throws no error, but the content is not current. Automation treats the absence of an error as success and marches on, so results computed from old data pile up quietly. This is exactly why count checks and integrity checks belong downstream.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Why the bash sandbox can only read files that are physically present, and how to tell on-demand materialization apart from a real error
✦A materialize-before-process design pattern built on the fact that bash VM paths and file-tool paths differ, with before/after code
✦A decision table for choosing how to read (direct bash, file tool, or copy-to-scratch) plus how to catch dropped data with counts and hashes
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
There is a second fact to bake into any automation: the path bash uses and the path the file tool uses differ, even for the same file. In Cowork, connected folders, the scratch work area, and read-only uploads each surface to bash as separate mount points.
So it is safest to detect "which path am I actually at" dynamically at the top of a script. Hardcode a session name and every path breaks the instant the next run lands in a different session.
# Don't hardcode the session name. Discover the mount dynamically.WS="$(ls -d /sessions/*/mnt/Workspace 2>/dev/null | head -1)"[ -z "$WS" ] && { echo "⚠️ workspace not found"; exit 1; }echo "WS=$WS"
That single line sidesteps the most common failure of all — "it worked yesterday, today every path errors." I make a point of putting it in Step 0 of every script.
Pattern 1: touch before you process
Here is the core idea. Since bash cannot request the body, the basic strategy is to make sure materialization has already happened before processing. If you are working interactively in Cowork, simply reading the target once with the file tool (which induces a download) is enough for subsequent bash to read the same path without trouble.
In a bash-only context like a scheduled task, you need a different move: run an operation whose side effect nudges the sync client into materializing, swallowing errors as you go.
# Materialization trigger (assuming the body exists in the cloud, try a read first)warm_file() { local f="$1" # It's fine if this fails; we just touch it lightly. With some sync # clients this read request becomes the download trigger. cat "$f" > /dev/null 2>&1 || true # Wait a moment, then judge whether the body is present for i in 1 2 3 4 5; do local sz; sz=$(stat -c '%s' "$f" 2>/dev/null || echo 0) [ "${sz:-0}" -gt 0 ] && return 0 sleep 2 done return 1}if warm_file "$WS/settings.json"; then echo "✅ materialized"else echo "⚠️ could not materialize: $WS/settings.json (not downloaded)"fi
The key is to confirm materialization succeeded via stat's byte count before moving on. Look only at cat's exit code and a zero-byte empty file still counts as "success."
Pattern 2: one-way copy into scratch
Make a cloud-synced folder the direct target of your work and, on top of read uncertainty, you also inherit write conflicts (collisions with another device's sync). If you want stable automation, use the synced folder only as an input source and do the real work in a scratch area.
The before/after below is the exact switch I made in a real automation script.
Before (process directly in the synced folder — unstable):
# Assemble outputs inside the synced foldercd "$WS/project"python3 build.py # silent empty output if inputs aren't materializedgit -C "$WS/project" commit -am "update" # prone to sync collisions
After (copy into scratch first, then process — stable):
SCRATCH="$HOME/work/project" # local work area outside the syncmkdir -p "$SCRATCH"# Copy one-way while materializing inputs (rsync requests the body)rsync -a --no-perms "$WS/project/inputs/" "$SCRATCH/inputs/" 2>&1 | tail -2cd "$SCRATCH"python3 build.py # runs reliably on real, present inputs# Write only the outputs back to the synced folder at the endrsync -a "$SCRATCH/dist/" "$WS/project/dist/"
Because rsync requests the body in order to copy it, the copy itself becomes the materialization trigger. Pushing the work outside the sync also prevents the accident where the sync client cuts in mid-build and rewrites a file. On disk-constrained environments, fold it up afterward with rm -rf "$SCRATCH" and you won't strain capacity either.
Choosing how to read
Let me organize the options by situation. There is no universal answer; you pick based on the execution context (interactive vs. unattended) and the nature of the target (a small config file vs. a large pile of assets).
Situation
Recommended
Why
Reading a single file in an interactive session
Open once with the file tool
Induces the download automatically; reliable
Reading a small config in an unattended task
Confirm via warm_file
Stays within bash and rules out empties via stat
Processing many files
rsync copy into scratch
Satisfies materialization and conflict-avoidance at once
Before pulling a large/heavy batch
State up front what you'll fetch
Avoids unintended mass downloads
Writing outputs back
One-way copy in a single pass at the end
Cuts off mid-process sync collisions
That last row, "state it before a large fetch," is also a matter of operational courtesy. Run an indiscriminate find ... -exec cat against a cloud-synced folder and you induce a download of the whole tree, dragging in both the network and the sync client. I narrow the target and log how many files and roughly how much volume I'm about to fetch before materializing.
Catch drops with counts and hashes
The worst pattern — silently reading a stale body — is largely preventable by wrapping the process in a light check before and after. Capture the input count and a hash, and stop when they diverge from expectations.
# Input sanity check (reject empty / zero-count early)N=$(find "$SCRATCH/inputs" -type f -name '*.json' | wc -l)[ "$N" -eq 0 ] && { echo "⚠️ zero inputs. likely failed materialization"; exit 1; }echo "inputs=$N files"# For critical files, confirm "non-empty, not corrupt" via a content hashH=$(sha256sum "$SCRATCH/inputs/manifest.json" | cut -c1-12)echo "manifest sha=$H"
A scheduled task gets recorded as "success" unless an error occurs, so holding a condition that deliberately fails with exit 1 is the most direct way to make silent failures visible. You notice a task that stopped on an error; nobody notices a task that ran to completion on emptiness.
A next step
Start by adding two things to the top of your own automation script: dynamic workspace detection and an input-count check. Just WS="$(ls -d /sessions/*/mnt/... | head -1)" plus a [ "$N" -eq 0 ] && exit 1 right before processing will let you catch both "visible but unreadable" and "ran to completion while stale" early. Cloud sync is convenient, but when you don't hold the timing of materialization, the ground under your automation gives way. Separate the input source from the work area, and touch before you process — keep that order and the stability of unattended runs changes dramatically.
If this trims one stumbling point for someone else handing their personal automation to Cowork, I'll be glad.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.