Context
Node.js 22 introduced module.enableCompileCache() and the NODE_COMPILE_CACHE environment variable, which persist compiled V8 bytecode to disk for faster module loading on subsequent startups. The gateway sets NODE_COMPILE_CACHE=$PREFIX/tmp/v8-cache to take advantage of this.
In practice, the compile cache on the Moto E2 accumulates files over time but provides minimal benefit. The gateway rarely restarts (uptimes of days or weeks are normal), so the cache is almost never read back. Meanwhile, the cache directory grows to 51 MB as V8 stores a bytecode file for every module that gets loaded, including lazily-loaded modules that may be loaded only once.
On a phone with roughly 3 GB of usable storage (after Android, Termux, and the OpenClaw installation), 51 MB is significant. Cleaning the cache and letting it rebuild on the next restart drops it to 27 MB, saving 24 MB.
There is also an ironic twist on this specific device: module.enableCompileCache() returns status 2 (ENABLED) but the actual cache files are often 0 bytes. V8's compile cache has known issues on ARM32, and the bytecode is never actually serialized to disk. The directory fills with thousands of empty or near-empty .cache files, wasting inodes and directory metadata space rather than actual byte storage. The cleanup is still worthwhile for filesystem overhead.
Implementation
Create the cleanup script:
#!/data/data/com.termux/files/usr/bin/bash
# $PREFIX/bin/clean-compile-cache.sh
# Clean Node.js V8 compile cache to reclaim disk space
CACHE_DIR="/data/data/com.termux/files/usr/tmp/v8-cache"
# Check size before cleanup
BEFORE=$(du -sh "$CACHE_DIR" 2>/dev/null | cut -f1)
COUNT=$(ls -1 "$CACHE_DIR" 2>/dev/null | wc -l)
# Remove and recreate the directory
rm -rf "$CACHE_DIR"
mkdir -p "$CACHE_DIR"
# Log the cleanup
echo "$(date '+%Y-%m-%d %H:%M') cache cleaned: $BEFORE ($COUNT files)" \
>> /data/data/com.termux/files/home/.openclaw-cache-logMake it executable:
chmod +x $PREFIX/bin/clean-compile-cache.shAdd the hourly cron job using busybox crontab:
# busybox crontab is at $PREFIX/bin/applets/crontab
$PREFIX/bin/applets/crontab -l > /tmp/current-cron 2>/dev/null || true
echo "0 * * * * $PREFIX/bin/clean-compile-cache.sh" >> /tmp/current-cron
$PREFIX/bin/applets/crontab /tmp/current-cron
rm /tmp/current-cronVerify the cron entry:
$PREFIX/bin/applets/crontab -l
# Should show: 0 * * * * /data/data/com.termux/files/usr/bin/clean-compile-cache.shEnsure crond is running (started automatically by the boot script, but verify manually):
pgrep -f crond || $PREFIX/bin/applets/crond -b -L /dev/nullThe NODE_COMPILE_CACHE environment variable is set in the gateway start script:
# In $PREFIX/bin/start-openclaw
export NODE_COMPILE_CACHE="/data/data/com.termux/files/usr/tmp/v8-cache"For reference, the compile cache can also be enabled programmatically in hijack.js:
// In hijack.js (loaded via -r flag before OpenClaw)
const mod = require('module');
if (typeof mod.enableCompileCache === 'function') {
const result = mod.enableCompileCache();
// result.status: 0 = FAILED, 1 = ALREADY_ENABLED, 2 = ENABLED
// On ARM32: returns 2 but files are 0 bytes
}Verification
du -sh $PREFIX/tmp/v8-cacheshows current cache size before cleanup.- Run the cleanup script:
$PREFIX/bin/clean-compile-cache.sh du -sh $PREFIX/tmp/v8-cacheshows4.0K(empty directory) after cleanup.- Restart the gateway and check:
du -sh $PREFIX/tmp/v8-cacherebuilds to approximately 27 MB. ls $PREFIX/tmp/v8-cache/ | wc -lshows the number of cache files (typically 200-400).cat ~/.openclaw-cache-logshows cleanup history with sizes and file counts.$PREFIX/bin/applets/crontab -lshows the scheduled cleanup job.- After 24 hours, the log should show multiple hourly cleanup entries.
Gotchas
- Cleaning the cache while the gateway is running is safe. Node.js opens cache files at module load time and does not hold file descriptors open afterward. The running process already has compiled bytecode in V8's in-memory code cache.
- The
mkdir -pafterrm -rfis essential. If the directory does not exist when the gateway next starts, Node.js silently disables compile caching rather than creating the directory itself. TheNODE_COMPILE_CACHEpath must already exist before the Node process starts. - On ARM32, compile cache files are often 0 bytes due to V8 serialization limitations on 32-bit platforms. The disk savings from cleanup come primarily from directory entry overhead, inode allocation, and filesystem journal entries rather than actual file content bytes.
- The hourly schedule is a balance. More frequent cleanup (every 15 minutes) wastes CPU cycles for minimal additional savings. Less frequent cleanup (daily) lets the cache grow back to 51 MB between cleanups. Hourly keeps it under 30 MB.
- Do not use
find ... -mtime +1 -deletefor selective age-based eviction. All cache files are created at gateway startup time, so they all have the same mtime. A full wipe-and-recreate is simpler and equally effective. - If crond is killed by the Android low memory killer, the cleanup job stops running silently. The boot script (
~/.termux/boot/start-pocketclaw.sh) restarts crond on boot, but an OOM kill between boots leaves no cleanup running. Check withpgrep -f crondif disk usage grows unexpectedly. - The cleanup script logs to
~/.openclaw-cache-log. This log itself grows unboundedly. Truncate it occasionally:tail -100 ~/.openclaw-cache-log > /tmp/cl && mv /tmp/cl ~/.openclaw-cache-log.
Result
The compile cache directory stays under 30 MB instead of growing unboundedly past 51 MB. On a storage-constrained phone, reclaiming 24 MB of disk space per cycle helps maintain headroom for logs, temporary files, and npm operations. The cleanup runs silently via cron with no impact on the running gateway, and the cache rebuilds automatically on the next restart.