Skip to content

OpenClaw driver: use ~/.openclaw instead of /app/state for container home #121

@mostlydev

Description

@mostlydev

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

  1. 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)
    
  2. Set environment variables to match:

    OPENCLAW_CONFIG_PATH=/root/.openclaw/config/openclaw.json
    OPENCLAW_STATE_DIR=/root/.openclaw
    
  3. 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.

  4. 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.

  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions