TL;DR: Upgrading from 2026.4.8 to 2026.4.15 tripped two restrictions that silence your config without a loud error: a tightened schema for tools.elevated.allowFrom.telegram that invalidates the whole file when it fails, and an fs-safe layer that refuses to traverse symlinks in the state-directory path. One is a config edit, the other is a bind mount.
Context Link to heading
I moved OpenClaw from a Jetson Nano to a VPS a couple of weeks ago. Everything ran on 2026.4.8. I upgraded to 2026.4.15 expecting a five-minute job. It took the rest of the evening.
Read the release notes before any minor-version bump. The npm package ships a flat changelog:
less "$(npm root -g)/openclaw/CHANGELOG.md"
Or pull them from GitHub:
gh release view v2026.4.15 --repo openclaw/openclaw
The online docs index hides these. Both gotchas below trace back to hardening spread across 2026.4.9 to 2026.4.15.
Schema changes can invalidate your whole config Link to heading
After upgrade, my scheduled jobs failed with API key not valid. Please pass a valid API key. The key in config was correct. A raw curl to the provider with the same key returned 200. Restarting the gateway did nothing.
Checking the config via the CLI surfaced the real problem:
Config invalid
Problem:
- tools.elevated.allowFrom.telegram: Invalid input: expected array, received boolean
A recent release tightened the schema. allowFrom.telegram: true used to mean “allow everyone”; the new schema wants an explicit array of user IDs. The gateway refused to load the whole config because of that one field, then fell back to an empty environment, which is why the API key looked invalid.
Fix:
openclaw config set tools.elevated.allowFrom.telegram "[YOUR_TELEGRAM_ID]" --strict-json
systemctl --user restart openclaw-gateway.service
After any upgrade, run openclaw doctor and openclaw config file before chasing downstream symptoms. One schema mismatch can disable everything else without a loud error.
Symlinked state directories break exec approvals Link to heading
My earlier migration post put OpenClaw’s state on a persistent block volume via a symlink:
ln -s /mnt/data/openclaw-home ~/.openclaw
This works for the gateway itself. It stops working when the agent tries to run commands that touch its own state directory. The logs showed:
[tools] exec failed: Refusing to traverse symlink in exec approvals path: /home/ubuntu/.openclaw
The 2026.4.x line routed agents.files.* and related reads through shared fs-safe helpers that reject symlink aliases, closing a TOCTOU swap between open and realpath. The check runs before the exec-policy layer. There’s no flag to relax it.
Replace the symlink with a bind mount. A real directory stays real:
systemctl --user stop openclaw-gateway.service
rm ~/.openclaw
mkdir -p ~/.openclaw
sudo mount --bind /mnt/data/openclaw-home ~/.openclaw
echo "/mnt/data/openclaw-home /home/ubuntu/.openclaw none bind 0 0" | sudo tee -a /etc/fstab
systemctl --user start openclaw-gateway.service
Verify:
test -L ~/.openclaw && echo "still a symlink" || echo "real dir"
mount | grep openclaw
If you’re writing a new migration guide, pick bind mount over symlink from day one.
Sandbox mode is not the same as exec approvals Link to heading
When the symlink-refusal error hit, the obvious-looking fix was:
openclaw config set agents.defaults.sandbox.mode off
That removes the agent-tool isolation layer. It does nothing for the exec-approvals path check, which runs before the sandbox decision. Turning sandbox off and leaving a symlinked state dir gets you the worst of both: less isolation and still-broken commands.
Two separate layers:
- Sandbox: isolates the agent’s tool execution from the host. Configured via
agents.defaults.sandbox.*. - Exec approvals: validates commands against an allowlist stored inside the state directory. Refuses to read approvals from a symlinked path. Configured via
openclaw exec-policy.
Either layer can block a command for independent reasons.
Further reading Link to heading
- Migrating OpenClaw from Jetson Nano to a VPS (the earlier migration post these notes update)
- OpenClaw documentation
- Linux bind mounts