hijack.js is the heart of PocketClaw. Running OpenClaw directly on Android 6 crashes immediately — os.networkInterfaces() triggers a segfault in bionic libc's NETLINK_ROUTE implementation. The file system layout doesn't match what OpenClaw expects (/root/ doesn't exist on Android). V8's garbage collector never runs aggressively enough for a 1 GB device. And 1547 modules load at startup even though only ~8 are used.
Rather than forking and patching OpenClaw's source code (which would break on updates), hijack.js is loaded before everything else via Node's -r (require) flag. It monkey-patches Node.js internals at the process level, making the entire runtime Android-compatible without changing a single line of OpenClaw code.
hijack.js is deployed at $ROOTFS/root/hijack.js (proot) or the Termux equivalent for native mode. It's loaded via:
export NODE_OPTIONS='-r /root/hijack.js --expose-gc --no-warnings --max-old-space-size=112 --max-semi-space-size=2'The file contains 7 major patches:
// 1. os.networkInterfaces() — bionic NETLINK_ROUTE crash fix
const _origNI = os.networkInterfaces;
os.networkInterfaces = function() {
try { return _origNI.call(os); }
catch(e) { return {}; }
};
// 2. fs path rewriting — /root/* -> actual Termux paths
function _fixPath(p) {
if (typeof p === 'string' && p.startsWith('/root/'))
return p.replace('/root/', PREFIX + '/');
return p;
}
// Applied to: readFile, writeFile, mkdir, stat, access, etc.
// 3. Periodic GC — frees ~15 MB per cycle
if (global.gc) {
setInterval(() => { global.gc(); }, 60000);
}
// 4. Lazy loading — Proxy-based deferred require for 37 prefixes
// (see Hack #18 for full implementation)
// 5. Dead stubs — 23 packages return empty objects instantly
// (see Hack #19)
// 6. Dashboard endpoints — /dashboard, /api/status injected into HTTP server
// (see Hack #48)
// 7. fs.promises patching — same path rewriting for async API
// (see Hack #13)# Verify hijack.js loads:
node -r /root/hijack.js -e "console.log('hijack loaded')"
# Expected: hijack loaded
# Test os.networkInterfaces() doesn't crash:
node -r /root/hijack.js -e "console.log(JSON.stringify(require('os').networkInterfaces()))"
# Expected: JSON object (possibly empty {} on Android 6)
# Test path rewriting:
node -r /root/hijack.js -e "require('fs').existsSync('/root/.openclaw') && console.log('path rewrite OK')"
# Expected: path rewrite OK
# Test GC runs (with --expose-gc):
node --expose-gc -r /root/hijack.js -e "setTimeout(() => console.log('GC timer active'), 2000)"-r, NOT inside the openclaw npm package. After npm update, the package is overwritten but hijack.js survives-r flag runs the file synchronously before any other code. This means hijack.js can patch require() itself to intercept all subsequent module loads--expose-gc is required for the periodic GC to work. Without it, global.gc is undefinedfs and fs.promises — they're separate objects in Node.jsprocess.env.PREFIX| Metric | Before | After |
|---|---|---|
| os.networkInterfaces() | Segfault | Returns {} safely |
| fs path resolution | Fails (/root not found) | Redirected to Termux |
| RAM (after 60s) | Grows unbounded | GC frees ~15 MB/cycle |
| Modules loaded at startup | 1547 | ~8 (rest lazy/dead) |
| Dashboard | None | /dashboard + /api/status |