Problem
The OpenClaw driver currently mounts state at /app/state and config at /app/config, with an explicit OPENCLAW_HOME=/app/state shim to work around upstream path resolution (openclaw/openclaw#29736). This fights the upstream convention where OpenClaw and its plugin ecosystem expect ~/.openclaw/ as the canonical home directory.
Current layout (internal/driver/openclaw/driver.go):
/app/config/openclaw.json ← bind-mounted directory (atomic writes)
/app/state ← tmpfs (uid=1000)
OPENCLAW_HOME=/app/state ← shim so exec-approvals finds ~/.openclaw/
OPENCLAW_CONFIG_PATH=/app/config/openclaw.json
OPENCLAW_STATE_DIR=/app/state
The OPENCLAW_HOME shim patches one known breakage (exec-approvals), but any plugin that resolves os.homedir() + '/.openclaw/' or hardcodes ~/.openclaw/ will still fail. As the plugin ecosystem grows, we'll be playing whack-a-mole with path assumptions.
Root cause
An earlier iteration used /root/.openclaw but moved to /app/state thinking a non-home path was cleaner. That move created the convention mismatch — the actual fix is to go back to ~/.openclaw and let the upstream convention work naturally.
Design choice: keep root
The container runs as root and should stay that way. Clawdapus treats the container as the trust boundary (untrusted workloads: reproducible, inspectable, diffable, killable). Root inside a read-only container with tmpfs overlays can't escape the sandbox. Running as root lets agents install tools at runtime (apt-get install, npm install -g, etc.), which is a real practical benefit. No need to create a dedicated user.
Since the container runs as root, ~ = /root and ~/.openclaw = /root/.openclaw.
Proposed fix
-
Move state and config under /root/.openclaw/:
/root/.openclaw/ ← tmpfs (replaces /app/state)
/root/.openclaw/config/ ← bind-mounted directory (replaces /app/config)
/root/.openclaw/cron/ ← bind-mounted directory (replaces /app/state/cron)
-
Set environment variables to match:
OPENCLAW_CONFIG_PATH=/root/.openclaw/config/openclaw.json
OPENCLAW_STATE_DIR=/root/.openclaw
-
Remove the OPENCLAW_HOME shim. With HOME=/root (the default), ~/.openclaw/ resolves to /root/.openclaw naturally and the exec-approvals workaround is no longer needed.
-
Update tmpfs uid/gid. Currently set to uid=1000,gid=1000 — since we're running as root (uid=0), this should change to uid=0,gid=0 or just use default mode bits.
-
Keep /claw as the workspace. The workspace (agents.defaults.workspace=/claw) is a Clawdapus concept, not an OpenClaw one — it stays where it is.
What stays the same
- Read-only container filesystem + tmpfs overlays (same security model)
- Running as root (same as today)
- Directory-level bind mounts for atomic write support
/claw workspace, /claw/AGENTS.md, /claw/CLAWDAPUS.md, /claw/skills/, /claw/memory mounts
CLAW_MANAGED, CLAW_MEMORY_DIR, CLAW_PERSONA_DIR env vars
Files to change
internal/driver/openclaw/driver.go — update mount paths, env vars, tmpfs paths, remove OPENCLAW_HOME shim
internal/driver/openclaw/driver_test.go — update path assertions, flip the /root/.openclaw guard to an /app/state guard
internal/driver/openclaw/baseimage.go — no changes expected (no USER change needed)
internal/driver/openclaw/config.go — no changes expected (workspace stays /claw)
Risk
- Existing containers will need to be recreated (
claw down && claw up). State in /app/state tmpfs is ephemeral by definition, so no data loss.
- Minor: if OpenClaw's
OPENCLAW_STATE_DIR takes precedence over ~/.openclaw in all code paths, the migration is purely cosmetic for upstream code. But plugin code typically resolves ~/.openclaw directly, which is the whole point of this change.
Problem
The OpenClaw driver currently mounts state at
/app/stateand config at/app/config, with an explicitOPENCLAW_HOME=/app/stateshim to work around upstream path resolution (openclaw/openclaw#29736). This fights the upstream convention where OpenClaw and its plugin ecosystem expect~/.openclaw/as the canonical home directory.Current layout (
internal/driver/openclaw/driver.go):The
OPENCLAW_HOMEshim patches one known breakage (exec-approvals), but any plugin that resolvesos.homedir() + '/.openclaw/'or hardcodes~/.openclaw/will still fail. As the plugin ecosystem grows, we'll be playing whack-a-mole with path assumptions.Root cause
An earlier iteration used
/root/.openclawbut moved to/app/statethinking a non-home path was cleaner. That move created the convention mismatch — the actual fix is to go back to~/.openclawand let the upstream convention work naturally.Design choice: keep root
The container runs as root and should stay that way. Clawdapus treats the container as the trust boundary (untrusted workloads: reproducible, inspectable, diffable, killable). Root inside a read-only container with tmpfs overlays can't escape the sandbox. Running as root lets agents install tools at runtime (
apt-get install,npm install -g, etc.), which is a real practical benefit. No need to create a dedicated user.Since the container runs as root,
~=/rootand~/.openclaw=/root/.openclaw.Proposed fix
Move state and config under
/root/.openclaw/:Set environment variables to match:
Remove the
OPENCLAW_HOMEshim. WithHOME=/root(the default),~/.openclaw/resolves to/root/.openclawnaturally and the exec-approvals workaround is no longer needed.Update tmpfs uid/gid. Currently set to
uid=1000,gid=1000— since we're running as root (uid=0), this should change touid=0,gid=0or just use default mode bits.Keep
/clawas the workspace. The workspace (agents.defaults.workspace=/claw) is a Clawdapus concept, not an OpenClaw one — it stays where it is.What stays the same
/clawworkspace,/claw/AGENTS.md,/claw/CLAWDAPUS.md,/claw/skills/,/claw/memorymountsCLAW_MANAGED,CLAW_MEMORY_DIR,CLAW_PERSONA_DIRenv varsFiles to change
internal/driver/openclaw/driver.go— update mount paths, env vars, tmpfs paths, removeOPENCLAW_HOMEshiminternal/driver/openclaw/driver_test.go— update path assertions, flip the/root/.openclawguard to an/app/stateguardinternal/driver/openclaw/baseimage.go— no changes expected (no USER change needed)internal/driver/openclaw/config.go— no changes expected (workspace stays/claw)Risk
claw down && claw up). State in/app/statetmpfs is ephemeral by definition, so no data loss.OPENCLAW_STATE_DIRtakes precedence over~/.openclawin all code paths, the migration is purely cosmetic for upstream code. But plugin code typically resolves~/.openclawdirectly, which is the whole point of this change.