Skip to content

fix: resolve EACCES error from incorrect bundled plugins directory - #2815

Merged
code-yeongyu merged 1 commit into
ultraworkers:mainfrom
camAtGitHub:eacces-bundled-plugins-path-fix
May 25, 2026
Merged

fix: resolve EACCES error from incorrect bundled plugins directory#2815
code-yeongyu merged 1 commit into
ultraworkers:mainfrom
camAtGitHub:eacces-bundled-plugins-path-fix

Conversation

@camAtGitHub

Copy link
Copy Markdown
Contributor

Cam's Explanation:
Hello, I found an issue with the resulting binary when following the quick start guide and doing:

# Inspect available commands
cd rust/
cargo run -p rusty-claude-cli -- --help

# Build the workspace (build a release binary instead) 
cargo build -r --workspace

when using the resulting binary: rust/target/release/claw, it would try to access /root/claw/claw-code/rust/crates/plugins/bundled, the program would error and fail due to permission denied and the program would then exit. This PR fixes that behavior.


Copilot's Explanation:
PluginManager::bundled_root() used env!("CARGO_MANIFEST_DIR") to locate bundled plugins, baking in the source-tree path at compile time (e.g. /root/claw/claw-code/rust/crates/plugins/bundled). A binary built from a root-owned checkout fails with EACCES for non-root users on startup.

Changes

  • bundled_root() resolution order (first existing path wins):

    1. <exe_dir>/../share/claw/plugins/bundled — standard FHS install
    2. <exe_dir>/bundled — simple relocated layout
    3. CARGO_MANIFEST_DIR/bundled — dev/source-tree fallback, only if it exists
    4. <exe_dir>/../share/claw/plugins/bundled — canonical default when nothing found
  • sync_bundled_plugins() error handlingEACCES on the auto-detected default bundled root is now treated as an empty plugin list rather than a fatal error. Explicit plugins.bundledRoot config overrides continue to surface errors normally.

  • Tests (4 new/updated):

    • default_bundled_root_is_not_blindly_cargo_manifest_dir — asserts the compile-time path is not returned when it doesn't exist
    • override_bundled_root_is_used_exactly — explicit config override is honored
    • explicit_nonexistent_bundled_root_does_not_fail — missing explicit path yields empty bundled list, no error
    • no_bundled_root_config_uses_auto_detection_without_panicbundled_root = None auto-detection path doesn't panic
// Before
pub fn bundled_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("bundled") // baked-in at compile time
}

// After: runtime resolution with dev fallback only when path exists
pub fn bundled_root() -> PathBuf {
    // 1. <exe_dir>/../share/claw/plugins/bundled
    // 2. <exe_dir>/bundled
    // 3. CARGO_MANIFEST_DIR/bundled  (only if exists — dev only)
    // 4. canonical share path as default
}
Original prompt

Create a fix in the camAtGitHub/claw-code fork (base main) for the upstream bug in ultraworkers/claw-code Rust workspace under rust/.

Background / bug

When building rust/target/release/claw from a repo cloned under a root-owned directory (e.g. /root/claw/claw-code), the runtime attempts to open the bundled plugins directory at startup from a compile-time path:

  • /root/claw/claw-code/rust/crates/plugins/bundled
    This happens because PluginManager::bundled_root() uses env!("CARGO_MANIFEST_DIR") and returns .../bundled.
    When the binary runs as a non-root user, this path may be unreadable, causing EACCES and early program exit.

The user validated that setting runtime config plugins.bundledRoot in project config (e.g. /opt/defender-spyder-workspace/.claw.json) works, but the default fallback remains broken.

Required change

Modify the default bundled plugins root resolution so it is suitable for installed/relocated binaries and does not depend on CARGO_MANIFEST_DIR unless explicitly intended.

Constraints

  • Preserve existing behavior when PluginManagerConfig.bundled_root is explicitly provided (e.g. via runtime config plugins.bundledRoot).
  • Prefer a runtime-located path, such as relative to std::env::current_exe() (e.g. <exe_dir>/../share/claw/plugins/bundled or <exe_dir>/bundled). Choose a reasonable convention.
  • It’s acceptable to keep a dev fallback to CARGO_MANIFEST_DIR/bundled, but only if it exists and as a last resort.
  • Ensure behavior is cross-platform where possible.

Tests

Add/adjust unit tests in the plugins crate to cover:

  • When bundled_root is not set, the default no longer blindly returns CARGO_MANIFEST_DIR/bundled.
  • When the repo-bundled path exists (dev scenario), the manager can still load repo bundled plugins.
  • When an override bundled_root is provided, that value is used.

Tests should be reliable in CI.

Acceptance criteria

  • Running a release-built claw binary from a root-owned repo checkout as a non-root user should no longer attempt to open /root/.../rust/crates/plugins/bundled by default.
  • Existing config override continues to work.
  • Tests pass.

Relevant code

  • rust/crates/plugins/src/lib.rs: PluginManager::bundled_root() currently uses env!("CARGO_MANIFEST_DIR").
  • sync_bundled_plugins() uses config.bundled_root.unwrap_or_else(Self::bundled_root).

Please implement the fix and open a PR on the fork.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: i cloned this repo and from the rust directory i ran: cargo build -r --workspace it worked.
For reference I cloned the repo to my local directory /root/claw/claw-code and the compiled binary during startup now has the fatal to non-root flaw that the binary is trying to access /root/claw/claw-code/rust/crates/plugins/bundled - it gets permission error and exits the program early into the startup.

My strace shows the issue:

openat(AT_FDCWD, "/opt/defender-spyder-workspace/.claw/settings.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/opt/defender-spyder-workspace/.claw/settings.local.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
getcwd("/opt/defender-spyder-workspace", 512) = 31
getcwd("/opt/defender-spyder-workspace", 512) = 31
mkdir("/opt/defender-spyder-workspace/.claw/sessions/c735e81ff5c567f3", 0777) = -1 EEXIST (File exists)
statx(AT_FDCWD, "/opt/defender-spyder-workspace/.claw/sessions/c735e81ff5c567f3", AT_STATX_SYNC_AS_STAT, STATX_ALL, {stx_mask=STATX_ALL, stx_attributes=0, stx_mode=S_IFDIR|0755, stx_size=6, ...}) = 0
getcwd("/opt/defender-spyder-workspace", 512) = 31
openat(AT_FDCWD, "/home/cm/.claw.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/cm/.claw/settings.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/opt/defender-spyder-workspace/.claw.json", O_RDONLY|O_CLOEXEC) = 3
statx(3, "", AT_STATX_SYNC_AS_STAT|AT_EMPTY_PATH, STATX_ALL, {stx_mask=STATX_ALL, stx_attributes=0, stx_mode=S_IFREG|0644, stx_size=56, ...}) = 0
read(3, "{\n  \"permissions\": {\n    \"defaul"..., 56) = 56
read(3, "", 32)                         = 0
close(3)                                = 0
openat(AT_FDCWD, "/opt/defender-spyder-workspace/.claw/settings.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/opt/defender-spyder-workspace/.claw/settings.local.json", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/root/claw/claw-code/rust/crates/plugins/bundled", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = -1 EACCES (Permission denied)
write(2, "error: ", 7error: )                  = 7
write(2, "Permission denied (os error 13)", 31Permission denied (os error 13)) = 31
write(2, "\n\nRun `claw --help` for usage.\n", 31

Run `claw --help` for usage.
) = 31
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_si...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

fixes: PluginManager::bundled_root() used env!("CARGO_MANIFEST_DIR") to locate bundled plugins, baking in the source-tree path at compile time. A binary built from a root-owned checkout fails with EACCES for non-root users on startup.
@code-yeongyu
code-yeongyu merged commit 96ddeca into ultraworkers:main May 25, 2026
@1716775457damn

Copy link
Copy Markdown

Good catch. Using env!(CARGO_MANIFEST_DIR) for runtime path resolution is a common pitfall in Rust binaries - the fix to derive the bundled plugins path from the actual binary location at runtime is the correct approach. This prevents EACCES when users build and run the binary outside the source tree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants