openclaw - 💡(How to fix) Fix [Bug]: write tool intermittently fails to follow symlinks — "directory component must be a directory"

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

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.

Error Message

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.

Root Cause

Suspected Root Cause

Fix Action

Fix / Workaround

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

Code Example

directory component must be a directory

---

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)
RAW_BUFFERClick to expand / collapse

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.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

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.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix [Bug]: write tool intermittently fails to follow symlinks — "directory component must be a directory"