Skip to content

exec-approvals: assertNoSymlinkParentsSync missing allowRootChildSymlink, breaks exec when ~/.openclaw is symlink #85890

Description

@kevinpop

Bug: assertNoExecApprovalsSymlinkParents doesn't pass allowRootChildSymlink: true

Environment

  • ClawX version: 0.4.6
  • OpenClaw version: 2026.5.12
  • OS: macOS 26.4.1 (Darwin 25E253, ARM64)
  • Node: v24.14.0

Problem

When ~/.openclaw is a symlink to another directory (e.g., ~/.local/openclaw), the exec-approvals module refuses to traverse it, causing all shell commands to fail globally across every agent.

Error:

Refusing to traverse symlink in exec approvals path must not traverse symlinked directory: /Users/kevin/.openclaw

Root Cause

In dist/exec-approvals-F42asPlK.js, the function assertNoExecApprovalsSymlinkParents calls assertNoSymlinkParentsSync (from regular-file-6GdZVPgG.js) with allowOutsideRoot: true but does not pass allowRootChildSymlink: true.

// Current (line 148-151):
assertNoSymlinkParentsSync({
    target: dir,
    allowOutsideRoot: true,
    // allowRootChildSymlink is missing!
});

The assertNoSymlinkParentsSync function already supports allowRootChildSymlink (line 51 of regular-file-6GdZVPgG.js), which is specifically designed for the case where a home directory's immediate child is a symlink — exactly our scenario.

Additional Issue

In the ensureDir function (line 138), fs.lstatSync(dir) is used to check the directory itself. If the directory is accessed through a symlink, this also rejects it. The fix is to use fs.statSync(dir) (which follows symlinks) instead.

Suggested Fix

Fix 1 — Pass allowRootChildSymlink:

assertNoSymlinkParentsSync({
    target: dir,
    allowOutsideRoot: true,
    allowRootChildSymlink: true,  // ← add this
});

Fix 2 — Use statSync for directory check:

// Before:
const dirStat = fs.lstatSync(dir);
// After:
const dirStat = fs.statSync(dir);

Why This Matters

~/.openclaw being a symlink is a common pattern:

  • Users who want to keep their config on a different volume
  • Dotfile managers (stow, chezmoi, etc.)
  • Users with limited home directory space

Without this fix, exec is completely broken for all agents, and the only workaround is manually patching the compiled JS file after every ClawX update.

Workaround

A launchd-based auto-repair script has been deployed that detects and patches the file on startup, but this is fragile and breaks on every ClawX update when the file hash changes.

Proposed Fix (minimal)

--- a/dist/exec-approvals-F42asPlK.js
+++ b/dist/exec-approvals-F42asPlK.js
@@ -148,6 +148,7 @@
     assertNoSymlinkParentsSync({
         target: dir,
         allowOutsideRoot: true,
+        allowRootChildSymlink: true,
     });
@@ -138,7 +138,7 @@
-    const dirStat = fs.lstatSync(dir);
+    const dirStat = fs.statSync(dir);

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:securitySecurity boundary, credential, authz, sandbox, or sensitive-data risk.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions