Skip to content

openclaw backup create fails on Android/Termux — EACCES: permission denied, link (hardlinks blocked by Android) #37

@schmosbyy

Description

@schmosbyy

openclaw backup create fails on all output paths on Android/Termux with an EACCES error. The command writes a .tmp file successfully but then fails on the atomic rename step, which uses fs.link() (hardlink). Android's app-private storage (ext4, app sandbox) blocks cross-file hardlinks, so this step always fails regardless of which directory is used.

Steps to Reproduce

bash
# Default (no --output flag)
openclaw backup create

# With explicit output dir
openclaw backup create --output ~/backups/

# With /usr/tmp
openclaw backup create --output /data/data/com.termux/files/usr/tmp/
```

All three fail with the same error pattern.

### Error
```
Error: EACCES: permission denied, link '/data/data/com.termux/files/home/2026-03-10T04-33-45.081Z-openclaw-backup.tar.gz.0c2c1b63-5771-4522-9fbc-85eed97dc288.tmp' -> '/data/data/com.termux/files/home/2026-03-10T04-33-45.081Z-openclaw-backup.tar.gz'

The .tmp file is created successfully — only the link() call to atomically rename it fails.

Root Cause

openclaw backup create uses fs.link(tmpPath, finalPath) for an atomic write. On Android, fs.link() is blocked by the OS for app-private storage (/data/data/com.termux/...), regardless of permissions. This is an Android security constraint, not a Termux configuration issue.

The fix is to replace fs.link() + fs.unlink() with fs.rename() for the atomic swap, which Android does support within the same filesystem. Alternatively, fall back to fs.rename() when fs.link() throws EACCES or EPERM.

Environment

Field | Value -- | -- OpenClaw version | 2026.3.8 (3caab92) Node.js | v22.20.0 npm | 10.9.2 Architecture | aarch64 Kernel | Linux localhost 4.14.113-25257816 #1 SMP PREEMPT aarch64 Android Filesystem type | ext2/ext3 (Android app-private storage) Termux PREFIX | /data/data/com.termux/files/usr TMPDIR | /data/data/com.termux/files/usr/tmp Install method | openclaw-android bootstrap (glibc/NIM, no proot)

Workaround

Manual tar achieves the same result:

bash
mkdir -p ~/backups
tar -czf ~/backups/$(date -u +%Y-%m-%dT%H-%M-%S)-openclaw-backup.tar.gz \
  -C ~ \
  .openclaw/openclaw.json \
  .openclaw/.env \
  .openclaw/secrets.json \
  .openclaw/credentials \
  .openclaw/identity \
  .openclaw/agents/main/agent \
  .openclaw/agents/coding/agent \
  .openclaw/agents/rachel/agent

Suggested Fix

In the backup write implementation, replace the atomic hardlink pattern:

js
// Current (breaks on Android)
await fs.link(tmpPath, finalPath);
await fs.unlink(tmpPath);

// Fix: use rename instead (atomic on same-fs, works on Android)
await fs.rename(tmpPath, finalPath);

Or add a fallback:

js
try {
  await fs.link(tmpPath, finalPath);
  await fs.unlink(tmpPath);
} catch (err) {
  if (err.code === 'EACCES' || err.code === 'EPERM') {
    await fs.rename(tmpPath, finalPath); // fallback for Android
  } else {
    throw err;
  }
}

Labels

bug, android, termux, backup

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