Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
The write tool intermittently fails when the target path traverses a symlink that resolves to a directory. The error message is directory component must be a directory, indicating the path validator sees the symlink as a file instead of resolving it.
Steps to reproduce
Create a symlink in the workspace: ln -s oc_system/memory /home/master/dev/memory
Target is a real directory: /home/master/dev/oc_system/memory/ exists and is drwxrwxr-x
Attempt write to memory/2026-05-20.md — intermittently fails with:
directory component must be a directory
Same call succeeds minutes later, or after writing to a nearby path first
Expected behavior
Expected Behavior:
When the write tool receives a path containing a symlink component that resolves to a directory, it should follow the symlink and write to the target — identical to how read, exec, and the OS itself treat symlinks. Specifically:
write("memory/2026-05-20.md") where memory/ is a symlink → oc_system/memory/ should behave identically to write("oc_system/memory/2026-05-20.md")
- The path validator should resolve each component via
realpath() / os.path.realpath() before checking isDirectory(), not stat() the symlink entry directly
- Behavior should be deterministic: same path, same result, every call — no cold-cache dependency
In short: symlinks should be transparent. The tool should never care whether a directory component is a real directory or a symlink resolving to one.
Actual behavior
Actual Behavior:
On cold cache (first call after session start or context compaction), the write tool's path validator stat()s the symlink entry itself rather than resolving it, sees S_IFLNK instead of S_IFDIR, and rejects the write with:
directory component must be a directory
The failure is intermittent — after successful writes to nearby paths warm the sandbox's internal path cache, subsequent calls to the same symlink path resolve correctly and succeed. This creates inconsistent behavior where:
write("memory/2026-05-20.md") → ❌ fails immediately after compaction
write("memory/2026-05-20.md") → ✅ succeeds minutes later
write("memory/test_new_file.md") → ✅ creating new files through symlink is more reliable
write("/home/master/dev/oc_system/memory/2026-05-20.md") → ✅ always works (bypasses symlink)
exec("cat > /home/master/dev/memory/2026-05-20.md") → ✅ always works (shell resolves symlinks natively)
Same filesystem, same target, same permissions — the only variable is whether the sandbox path cache has been warmed.
OpenClaw version
2026.5.x (current)
Operating system
Linux 6.8.0-111-generic (x64) - Ubuntu 22 LTS
Install method
npm global
Model
gemma4:31b
Provider / routing chain
ollama loalhost
Additional provider/model setup details
No response
Logs, screenshots, and evidence
The symlink is valid:
lrwxrwxrwx 1 master master 16 May 11 21:24 /home/master/dev/memory -> oc_system/memory
Writing to the real path ALWAYS works
drwxrwxr-x 2 master master 4096 May 20 18:24 /home/master/dev/oc_system/memory/
Writing through symlinks always works
write("/home/master/dev/oc_system/memory/2026-05-20.md") → ✅
WRiting through the symlink works intermittently
write("memory/2026-05-20.md") → ❌ (fails after context compaction or session start)
write("memory/2026-05-20.md") → ✅ (works after a few successful writes to nearby paths)
write("memory/test.md") → ✅ (creating NEW files through symlink seems more reliable)
other symlink writes work fine:
write("memory_test_symlink/test.md") → ✅ (different symlink to same target works)
Impact and severity
Severity: Low
Impact: Low
Here's the reasoning:
Impact — Low:
- Only affects workspaces that use symlinks under the
memory/ path (which is an OpenClaw convention, not a requirement)
- The
exec fallback works 100% of the time — no data loss possible
- The tool doesn't corrupt data, it just refuses the write
- Only the
write tool is affected; read and exec resolve symlinks fine
- The failure self-heals after cache warming
Severity — Low:
- Intermittent, not deterministic — can't reliably reproduce on every call
- Has a trivial workaround (retry, or use
exec to write to the real path)
- No security implication (symlink already points to an allowed path)
- No data loss or corruption risk
- Doesn't affect the
memory/ read path at all (session context loads fine)
- The only real annoyance is burning extra turns on retry or falling back to
exec
Why not Medium:
- It's not a regression from expected behavior — the
memory/ symlink setup is non-standard (pointing into oc_system/)
- Standard workspace setups with real
memory/ directories would never hit this
- The failure mode is noisy but harmless
The case for tracking it at all: Even though it's low/low, it's a real sandbox correctness bug — the path validator should resolve symlinks before checking isDirectory(). If someone later adds symlinks for other reasons (shared configs, cross-workspace references, Docker volume mounts), this will bite again. Worth fixing, not worth rushing.
Additional information
Suspected Root Cause
The write tool's path sandbox validates each directory component before writing. On cold cache (first call after compaction/session start), it appears to stat() the symlink entry itself rather than os.path.realpath() / os.path.realpath(), getting S_IFLNK instead of S_IFDIR, and rejecting the path.
After successful writes warm the sandbox's path cache, subsequent calls resolve correctly.
Suggested Fix
In the path validator, ensure symlink components are resolved with os.path.realpath() or fs.realpathSync() before checking isDirectory(). The fix should be in whatever function walks path components and validates each one is a directory.
Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
The write tool intermittently fails when the target path traverses a symlink that resolves to a directory. The error message is directory component must be a directory, indicating the path validator sees the symlink as a file instead of resolving it.
Steps to reproduce
Create a symlink in the workspace: ln -s oc_system/memory /home/master/dev/memory
Target is a real directory: /home/master/dev/oc_system/memory/ exists and is drwxrwxr-x
Attempt write to memory/2026-05-20.md — intermittently fails with:
directory component must be a directory
Same call succeeds minutes later, or after writing to a nearby path first
Expected behavior
Expected Behavior:
When the
writetool receives a path containing a symlink component that resolves to a directory, it should follow the symlink and write to the target — identical to howread,exec, and the OS itself treat symlinks. Specifically:write("memory/2026-05-20.md")wherememory/is a symlink →oc_system/memory/should behave identically towrite("oc_system/memory/2026-05-20.md")realpath()/os.path.realpath()before checkingisDirectory(), notstat()the symlink entry directlyIn short: symlinks should be transparent. The tool should never care whether a directory component is a real directory or a symlink resolving to one.
Actual behavior
Actual Behavior:
On cold cache (first call after session start or context compaction), the
writetool's path validatorstat()s the symlink entry itself rather than resolving it, seesS_IFLNKinstead ofS_IFDIR, and rejects the write with:The failure is intermittent — after successful writes to nearby paths warm the sandbox's internal path cache, subsequent calls to the same symlink path resolve correctly and succeed. This creates inconsistent behavior where:
write("memory/2026-05-20.md")→ ❌ fails immediately after compactionwrite("memory/2026-05-20.md")→ ✅ succeeds minutes laterwrite("memory/test_new_file.md")→ ✅ creating new files through symlink is more reliablewrite("/home/master/dev/oc_system/memory/2026-05-20.md")→ ✅ always works (bypasses symlink)exec("cat > /home/master/dev/memory/2026-05-20.md")→ ✅ always works (shell resolves symlinks natively)Same filesystem, same target, same permissions — the only variable is whether the sandbox path cache has been warmed.
OpenClaw version
2026.5.x (current)
Operating system
Linux 6.8.0-111-generic (x64) - Ubuntu 22 LTS
Install method
npm global
Model
gemma4:31b
Provider / routing chain
ollama loalhost
Additional provider/model setup details
No response
Logs, screenshots, and evidence
Impact and severity
Severity: Low
Impact: Low
Here's the reasoning:
Impact — Low:
memory/path (which is an OpenClaw convention, not a requirement)execfallback works 100% of the time — no data loss possiblewritetool is affected;readandexecresolve symlinks fineSeverity — Low:
execto write to the real path)memory/read path at all (session context loads fine)execWhy not Medium:
memory/symlink setup is non-standard (pointing intooc_system/)memory/directories would never hit thisThe case for tracking it at all: Even though it's low/low, it's a real sandbox correctness bug — the path validator should resolve symlinks before checking
isDirectory(). If someone later adds symlinks for other reasons (shared configs, cross-workspace references, Docker volume mounts), this will bite again. Worth fixing, not worth rushing.Additional information
Suspected Root Cause
The write tool's path sandbox validates each directory component before writing. On cold cache (first call after compaction/session start), it appears to stat() the symlink entry itself rather than os.path.realpath() / os.path.realpath(), getting S_IFLNK instead of S_IFDIR, and rejecting the path.
After successful writes warm the sandbox's path cache, subsequent calls resolve correctly.
Suggested Fix
In the path validator, ensure symlink components are resolved with os.path.realpath() or fs.realpathSync() before checking isDirectory(). The fix should be in whatever function walks path components and validates each one is a directory.