Skip to content

fix(sandbox): allow openclaw.json writes for channel configuration (fixes #606)#637

Open
kagura-agent wants to merge 1 commit intoNVIDIA:mainfrom
kagura-agent:fix/606-discord-bot-token-set-error-ea
Open

fix(sandbox): allow openclaw.json writes for channel configuration (fixes #606)#637
kagura-agent wants to merge 1 commit intoNVIDIA:mainfrom
kagura-agent:fix/606-discord-bot-token-set-error-ea

Conversation

@kagura-agent
Copy link

@kagura-agent kagura-agent commented Mar 22, 2026

Summary

At sandbox startup, copy the immutable openclaw.json to the writable state directory (~/.openclaw-data/) and set OPENCLAW_CONFIG_PATH so OpenClaw reads/writes the writable copy. The original locked file stays untouched.

Problem

openclaw.json is locked (root:root 444) at build time to prevent agent tampering (#514, #588). However, users running openclaw onboard inside the sandbox to configure channels (e.g. adding a Discord bot token) hit:

Error: EACCES: permission denied, copyfile '/sandbox/.openclaw/openclaw.json.3911.tmp' -> '/sandbox/.openclaw/openclaw.json'

PR #601 addressed the specific case of passing DISCORD_BOT_TOKEN via env vars, but the underlying issue remains: any openclaw onboard or config write inside the sandbox fails with EACCES.

Solution

Add prepare_writable_config() to nemoclaw-start.sh:

  1. Copy the immutable ~/.openclaw/openclaw.json~/.openclaw-data/openclaw.json (the writable state dir that already exists for agents, plugins, etc.)
  2. Export OPENCLAW_CONFIG_PATH pointing to the writable copy
  3. OpenClaw's config system natively supports this env var — all reads and writes go to the redirected path
  4. The original immutable file stays locked under Landlock + DAC protection

This is idempotent: the copy only happens if the writable file doesn't already exist, so container restarts won't overwrite user changes.

Changes

  • scripts/nemoclaw-start.sh: add prepare_writable_config(); update print_dashboard_urls to respect OPENCLAW_CONFIG_PATH
  • test/e2e-test.sh: add test 11 verifying the writable overlay works and the immutable original stays untouched

Security considerations

  • The immutable /sandbox/.openclaw/openclaw.json remains root:root 444 — unchanged
  • The Landlock read-only policy on /sandbox/.openclaw continues to protect the original
  • The writable copy lives in ~/.openclaw-data/ which is already the designated writable area for sandbox state
  • OPENCLAW_CONFIG_PATH is set by the entrypoint (not by the agent), so the agent cannot redirect it to an arbitrary path

Test plan

  • Unit tests pass (193/194 — the 1 pre-existing failure in install-preflight.test.js is unrelated)
  • New e2e test 11 verifies writable config overlay
  • Run openclaw onboard inside sandbox, configure Discord — no EACCES error
  • Verify /sandbox/.openclaw/openclaw.json remains immutable after config writes

Closes #606

Summary by CodeRabbit

  • New Features

    • Configuration can now be safely modified at runtime while preserving the original settings.
  • Tests

    • Added end-to-end test to verify configuration persistence and integrity during runtime operations.

…ixes NVIDIA#606)

openclaw.json is locked (root:root 444) at build time to prevent agent
tampering (NVIDIA#514, NVIDIA#588).  However, users legitimately need to modify
config at runtime — e.g. running `openclaw onboard` to add a Discord
bot token.  The atomic write (tmp → copyfile → rename) in OpenClaw's
config writer fails with EACCES against the immutable file.

PR NVIDIA#601 addressed the env-var path (passing DISCORD_BOT_TOKEN into the
sandbox), but the underlying issue remains: any `openclaw onboard` or
`/config` write inside the sandbox hits the same EACCES error.

Fix: at sandbox startup, copy the immutable openclaw.json to the
writable state directory (~/.openclaw-data/) and set
OPENCLAW_CONFIG_PATH to redirect all OpenClaw config reads/writes to
the copy.  The original immutable file stays intact as a read-only
reference; the Landlock policy on /sandbox/.openclaw continues to
protect it.

Changes:
- nemoclaw-start.sh: add prepare_writable_config() that copies the
  locked config to ~/.openclaw-data/openclaw.json and exports
  OPENCLAW_CONFIG_PATH; update print_dashboard_urls to respect the
  env var
- e2e-test.sh: add test 11 verifying writable overlay works and
  immutable original stays untouched
@coderabbitai
Copy link

coderabbitai bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

The changes implement a writable config overlay pattern to address read-only permission issues. A new prepare_writable_config function creates a writable copy of the baked config file, exports its path as OPENCLAW_CONFIG_PATH, and updates the dashboard URL printer to use this redirected path. An accompanying E2E test validates the overlay functionality.

Changes

Cohort / File(s) Summary
Config Writable Overlay Setup
scripts/nemoclaw-start.sh
Added prepare_writable_config function that copies the immutable config from ~/.openclaw/openclaw.json to ~/.openclaw-data/openclaw.json with 600 permissions and exports OPENCLAW_CONFIG_PATH. Updated print_dashboard_urls to read from the exported path when available. Integrated function into startup sequence before write_auth_profile.
Config Overlay E2E Test
test/e2e-test.sh
Added test #11 validating the writable config overlay pattern: verifies writable copy creation, Discord token write success, persistence of written token, and immutability of the original config file.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A config once rigid, now flows with grace,
With writable copies in their own space,
Tokens and settings, no longer denied,
The overlay pattern, our guard and our guide! 📝✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: allowing writes to openclaw.json for channel configuration, and references the fixed issue #606.
Linked Issues check ✅ Passed The PR fully addresses issue #606 by implementing a writable config overlay mechanism that allows legitimate runtime writes while preserving the immutable original file protection.
Out of Scope Changes check ✅ Passed All changes directly support the core objective of issue #606: the prepare_writable_config function, OPENCLAW_CONFIG_PATH export, print_dashboard_urls update, and new E2E test are all in scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can enable review details to help with troubleshooting, context usage and more.

Enable the reviews.review_details setting to include review details such as the model used, the time taken for each step and more in the review comments.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/nemoclaw-start.sh`:
- Around line 140-143: The copy can fail if the parent directory of the target
writable config doesn't exist; before copying immutable_cfg to writable_cfg add
creation of the parent directory for writable_cfg (use mkdir -p on the dirname
of the writable_cfg variable) so the cp will succeed, then proceed to cp
"$immutable_cfg" "$writable_cfg" and chmod 600 "$writable_cfg"; ensure the mkdir
is no-op if the directory already exists and occurs only when writable_cfg is
missing.

In `@test/e2e-test.sh`:
- Around line 258-261: The test copies an immutable config to writable using
shutil.copy2(immutable, writable) but never ensures the parent directory
(~/.openclaw-data) exists, causing copy to fail; update the test before the copy
(around the prepare_writable_config() simulation) to create the writable file's
parent directory (os.path.dirname(writable) or the ~/.openclaw-data path) if it
doesn't exist (mkdir -p semantics) and set proper permissions, then proceed to
assert the immutable exists and copy via shutil.copy2; this mirrors
prepare_writable_config() behavior and prevents the missing-directory error for
variables immutable and writable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ef8bb28a-7434-47a0-b51b-e890f8f48863

📥 Commits

Reviewing files that changed from the base of the PR and between 04012f7 and 4a82743.

📒 Files selected for processing (2)
  • scripts/nemoclaw-start.sh
  • test/e2e-test.sh

Comment on lines +140 to +143
if [ ! -f "$writable_cfg" ] && [ -f "$immutable_cfg" ]; then
cp "$immutable_cfg" "$writable_cfg"
chmod 600 "$writable_cfg"
fi
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing parent directory creation for writable config.

The cp command on line 141 will fail with "No such file or directory" if ~/.openclaw-data/ doesn't exist. Add directory creation before copying.

🛠️ Proposed fix
   if [ ! -f "$writable_cfg" ] && [ -f "$immutable_cfg" ]; then
+    mkdir -p "$(dirname "$writable_cfg")"
     cp "$immutable_cfg" "$writable_cfg"
     chmod 600 "$writable_cfg"
   fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ ! -f "$writable_cfg" ] && [ -f "$immutable_cfg" ]; then
cp "$immutable_cfg" "$writable_cfg"
chmod 600 "$writable_cfg"
fi
if [ ! -f "$writable_cfg" ] && [ -f "$immutable_cfg" ]; then
mkdir -p "$(dirname "$writable_cfg")"
cp "$immutable_cfg" "$writable_cfg"
chmod 600 "$writable_cfg"
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/nemoclaw-start.sh` around lines 140 - 143, The copy can fail if the
parent directory of the target writable config doesn't exist; before copying
immutable_cfg to writable_cfg add creation of the parent directory for
writable_cfg (use mkdir -p on the dirname of the writable_cfg variable) so the
cp will succeed, then proceed to cp "$immutable_cfg" "$writable_cfg" and chmod
600 "$writable_cfg"; ensure the mkdir is no-op if the directory already exists
and occurs only when writable_cfg is missing.

Comment on lines +258 to +261
# Simulate what prepare_writable_config does
assert os.path.isfile(immutable), f'Immutable config missing: {immutable}'
shutil.copy2(immutable, writable)
os.chmod(writable, 0o600)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for creation of .openclaw-data directory
rg -n '\.openclaw-data' --type sh --type py

Repository: NVIDIA/NemoClaw

Length of output: 235


🏁 Script executed:

# Search for any mkdir/makedirs operations
rg -n '(mkdir|makedirs)' --type sh --type py

Repository: NVIDIA/NemoClaw

Length of output: 1597


🏁 Script executed:

# Look at prepare_writable_config function in nemoclaw-start.sh
sed -n '130,150p' scripts/nemoclaw-start.sh

Repository: NVIDIA/NemoClaw

Length of output: 889


🏁 Script executed:

# Examine the complete test setup around lines 244-288
sed -n '244,288p' test/e2e-test.sh

Repository: NVIDIA/NemoClaw

Length of output: 1692


🏁 Script executed:

# Search for test setup/initialization
sed -n '1,243p' test/e2e-test.sh | grep -n -A2 -B2 "openclaw-data\|mkdirSync\|makedirs"

Repository: NVIDIA/NemoClaw

Length of output: 368


🏁 Script executed:

# Check if there's a setup.sh or fixture that creates directories
git ls-files | grep -E "(setup|fixture|init)" | head -20

Repository: NVIDIA/NemoClaw

Length of output: 392


Missing directory creation before copying config.

The test and prepare_writable_config() in nemoclaw-start.sh both assume ~/.openclaw-data/ exists. The .openclaw directory is created in test setup (line 196), but .openclaw-data is never created anywhere. When shutil.copy2() executes without the parent directory, it will fail.

🛠️ Proposed fix for the test
 # Simulate what prepare_writable_config does
 assert os.path.isfile(immutable), f'Immutable config missing: {immutable}'
+os.makedirs(os.path.dirname(writable), exist_ok=True)
 shutil.copy2(immutable, writable)
 os.chmod(writable, 0o600)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Simulate what prepare_writable_config does
assert os.path.isfile(immutable), f'Immutable config missing: {immutable}'
shutil.copy2(immutable, writable)
os.chmod(writable, 0o600)
# Simulate what prepare_writable_config does
assert os.path.isfile(immutable), f'Immutable config missing: {immutable}'
os.makedirs(os.path.dirname(writable), exist_ok=True)
shutil.copy2(immutable, writable)
os.chmod(writable, 0o600)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e-test.sh` around lines 258 - 261, The test copies an immutable config
to writable using shutil.copy2(immutable, writable) but never ensures the parent
directory (~/.openclaw-data) exists, causing copy to fail; update the test
before the copy (around the prepare_writable_config() simulation) to create the
writable file's parent directory (os.path.dirname(writable) or the
~/.openclaw-data path) if it doesn't exist (mkdir -p semantics) and set proper
permissions, then proceed to assert the immutable exists and copy via
shutil.copy2; this mirrors prepare_writable_config() behavior and prevents the
missing-directory error for variables immutable and writable.

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.

Discord bot token set - Error: EACCES: permission denied, copyfile '/sandbox/.openclaw/openclaw.json

1 participant