From 54ec46691262171e34366f60d0e581614d40d1a0 Mon Sep 17 00:00:00 2001 From: kagura-agent Date: Mon, 30 Mar 2026 18:42:06 +0800 Subject: [PATCH 1/4] fix(install): ensure .openclaw-data ownership for sandbox user (fixes #692) The native curl installer may create .openclaw-data directories as root, causing EACCES when openclaw (running as the sandbox user) tries to write device-auth.json to the identity directory. Add a startup ownership check to nemoclaw-start.sh that: - Creates any missing writable subdirectories (mirrors Dockerfile setup) - Fixes ownership if files are not owned by the current user - Creates the identity symlink if missing on native installs The Docker path is unaffected (Dockerfile already sets correct ownership). Resubmitted as requested by @mnsami (original: #698). --- scripts/nemoclaw-start.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/scripts/nemoclaw-start.sh b/scripts/nemoclaw-start.sh index 9e316b395..dcbce6d46 100755 --- a/scripts/nemoclaw-start.sh +++ b/scripts/nemoclaw-start.sh @@ -297,6 +297,29 @@ if [ "$(id -u)" -ne 0 ]; then if ! verify_config_integrity; then echo "[SECURITY WARNING] Config integrity check failed — proceeding anyway (non-root mode)" fi + + # Ensure writable state directories exist and are owned by the current user. + # The Docker build (Dockerfile) sets this up correctly, but the native curl + # installer may create these directories as root, causing EACCES when openclaw + # tries to write device-auth.json or other state files. Ref: #692 + fix_openclaw_data_ownership() { + local data_dir="${HOME}/.openclaw-data" + local openclaw_dir="${HOME}/.openclaw" + [ -d "$data_dir" ] || return 0 + local subdirs="agents/main/agent extensions workspace skills hooks identity devices canvas cron" + for sub in $subdirs; do + mkdir -p "${data_dir}/${sub}" 2>/dev/null || true + done + if find "$data_dir" -maxdepth 0 ! -user "$(id -u)" -print -quit 2>/dev/null | grep -q .; then + chown -R "$(id -u):$(id -g)" "$data_dir" 2>/dev/null || true + echo "[setup] fixed ownership on ${data_dir}" + fi + if [ ! -e "${openclaw_dir}/identity" ] && [ -d "${data_dir}/identity" ]; then + ln -sf "${data_dir}/identity" "${openclaw_dir}/identity" 2>/dev/null || true + echo "[setup] created identity symlink" + fi + } + fix_openclaw_data_ownership write_auth_profile if [ ${#NEMOCLAW_CMD[@]} -gt 0 ]; then From 85bcc6976014904c3cf789b5bc088b3900f09c35 Mon Sep 17 00:00:00 2001 From: kagura-agent Date: Mon, 30 Mar 2026 20:32:11 +0800 Subject: [PATCH 2/4] fix(install): ensure parent dir exists before identity symlink Add mkdir -p for openclaw_dir before ln -sf, in case /sandbox/.openclaw doesn't exist yet (e.g. fresh native installs). Addresses CodeRabbit review nitpick. --- scripts/nemoclaw-start.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/nemoclaw-start.sh b/scripts/nemoclaw-start.sh index dcbce6d46..e1868ea8e 100755 --- a/scripts/nemoclaw-start.sh +++ b/scripts/nemoclaw-start.sh @@ -315,6 +315,7 @@ if [ "$(id -u)" -ne 0 ]; then echo "[setup] fixed ownership on ${data_dir}" fi if [ ! -e "${openclaw_dir}/identity" ] && [ -d "${data_dir}/identity" ]; then + mkdir -p "${openclaw_dir}" 2>/dev/null || true ln -sf "${data_dir}/identity" "${openclaw_dir}/identity" 2>/dev/null || true echo "[setup] created identity symlink" fi From 1cc6be5864a9fc3c3cef94696e4d73ef02a48471 Mon Sep 17 00:00:00 2001 From: kagura-agent Date: Mon, 30 Mar 2026 20:40:44 +0800 Subject: [PATCH 3/4] fix: deep ownership scan and robust identity symlink repair Address CodeRabbit review feedback: 1. Ownership check: remove -maxdepth 0 so find scans all descendants for wrong-uid files; surface chown failure instead of suppressing 2. Identity symlink: handle stale/broken symlinks and wrong targets by comparing readlink -f; warn if path exists as non-symlink --- scripts/nemoclaw-start.sh | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/scripts/nemoclaw-start.sh b/scripts/nemoclaw-start.sh index e1868ea8e..16788b3ba 100755 --- a/scripts/nemoclaw-start.sh +++ b/scripts/nemoclaw-start.sh @@ -310,14 +310,29 @@ if [ "$(id -u)" -ne 0 ]; then for sub in $subdirs; do mkdir -p "${data_dir}/${sub}" 2>/dev/null || true done - if find "$data_dir" -maxdepth 0 ! -user "$(id -u)" -print -quit 2>/dev/null | grep -q .; then - chown -R "$(id -u):$(id -g)" "$data_dir" 2>/dev/null || true - echo "[setup] fixed ownership on ${data_dir}" + if find "$data_dir" ! -uid "$(id -u)" -print -quit 2>/dev/null | grep -q .; then + if chown -R "$(id -u):$(id -g)" "$data_dir" 2>/dev/null; then + echo "[setup] fixed ownership on ${data_dir}" + else + echo "[setup] could not fix ownership on ${data_dir}; writes may fail" >&2 + fi fi - if [ ! -e "${openclaw_dir}/identity" ] && [ -d "${data_dir}/identity" ]; then + if [ -d "${data_dir}/identity" ]; then mkdir -p "${openclaw_dir}" 2>/dev/null || true - ln -sf "${data_dir}/identity" "${openclaw_dir}/identity" 2>/dev/null || true - echo "[setup] created identity symlink" + if [ -L "${openclaw_dir}/identity" ]; then + local current_target expected_target + current_target="$(readlink -f "${openclaw_dir}/identity" 2>/dev/null || true)" + expected_target="$(readlink -f "${data_dir}/identity" 2>/dev/null || true)" + if [ "$current_target" != "$expected_target" ]; then + ln -snf "${data_dir}/identity" "${openclaw_dir}/identity" 2>/dev/null || true + echo "[setup] repaired identity symlink" + fi + elif [ ! -e "${openclaw_dir}/identity" ]; then + ln -snf "${data_dir}/identity" "${openclaw_dir}/identity" 2>/dev/null || true + echo "[setup] created identity symlink" + else + echo "[setup] ${openclaw_dir}/identity exists and is not a symlink; leaving as-is" >&2 + fi fi } fix_openclaw_data_ownership From d2cf5db2af94145dcfd75fe0f3028a25f899eeac Mon Sep 17 00:00:00 2001 From: kagura-agent Date: Tue, 31 Mar 2026 14:10:32 +0800 Subject: [PATCH 4/4] fix: consistent error reporting for symlink ops and remediate non-symlink identity - Wrap ln -snf calls in if/then to only log success when the command actually succeeds, matching the chown pattern (CodeRabbit review) - Replace 'leaving as-is' branch with backup+replace logic: move the non-symlink identity path aside with a timestamped backup, then create the correct symlink (CodeRabbit review) --- scripts/nemoclaw-start.sh | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/scripts/nemoclaw-start.sh b/scripts/nemoclaw-start.sh index 66e58a16b..0460e1a8a 100755 --- a/scripts/nemoclaw-start.sh +++ b/scripts/nemoclaw-start.sh @@ -325,14 +325,27 @@ if [ "$(id -u)" -ne 0 ]; then current_target="$(readlink -f "${openclaw_dir}/identity" 2>/dev/null || true)" expected_target="$(readlink -f "${data_dir}/identity" 2>/dev/null || true)" if [ "$current_target" != "$expected_target" ]; then - ln -snf "${data_dir}/identity" "${openclaw_dir}/identity" 2>/dev/null || true - echo "[setup] repaired identity symlink" + if ln -snf "${data_dir}/identity" "${openclaw_dir}/identity" 2>/dev/null; then + echo "[setup] repaired identity symlink" + else + echo "[setup] could not repair identity symlink" >&2 + fi fi elif [ ! -e "${openclaw_dir}/identity" ]; then - ln -snf "${data_dir}/identity" "${openclaw_dir}/identity" 2>/dev/null || true - echo "[setup] created identity symlink" + if ln -snf "${data_dir}/identity" "${openclaw_dir}/identity" 2>/dev/null; then + echo "[setup] created identity symlink" + else + echo "[setup] could not create identity symlink" >&2 + fi else - echo "[setup] ${openclaw_dir}/identity exists and is not a symlink; leaving as-is" >&2 + local backup + backup="${openclaw_dir}/identity.bak.$(date +%s)" + if mv "${openclaw_dir}/identity" "$backup" 2>/dev/null \ + && ln -snf "${data_dir}/identity" "${openclaw_dir}/identity" 2>/dev/null; then + echo "[setup] replaced non-symlink identity path (backup: ${backup})" + else + echo "[setup] could not replace ${openclaw_dir}/identity; writes may fail" >&2 + fi fi fi }