Target: latest stable upstream OpenCode v2 running natively in Termux
(aarch64 Android, no proot), installed as opencode2 (the installer links
opencode → opencode2, so v2 replaces v1 as the default).
Result: opencode v2.0.24-android-termux.1 with working TUI.
- Termux is bionic (Android libc), not glibc. Official
linux-arm64binaries die withSIGSYS/missing glibc. Prior art (guysoft/opencode-termux,dextune,Thr45hx) confirms: v1 needed Bun+WebKit cross-compiled from source; glibc-runner/patchelf shims are fragile. - Turning point: Bun ≥1.4 ships official
androidtargets (bun-linux-aarch64-android.zip). OpenCode v2 builds withbun build --compile, which accepts an android base binary asexecutablePath— so the CLI needs no source-built Bun/WebKit anymore. Only the renderer (libopentui.so, Zig-built) still needs compiling.
Installed guysoft's prebuilt opencode2 v1.0.2 (app 2.0.0-android-termux.1,
Android linker /system/bin/linker64, SHA256-verified) via filesystem copy +
native dpkg -i for the ripgrep dependency. Proved the shape of a working
install: $PREFIX/bin/opencode2 wrapper + $PREFIX/libexec/opencode2/.
Upstream repo: anomalyco/opencode, tag v2.0.24
(@opentui/core 0.5.14, packageManager bun@1.4.2).
cli-build-android.patch(packages/cli/script/build.ts,packages/cli/script/opencode-pty.ts):- allow
abi "android", add{linux, arm64, android}target (resolves to thebun-linux-aarch64-androidbase asset); - parcel-watcher binding: use official
@parcel/watcher-android-arm64(not thelinux-arm64-androidname, which does not exist); resolveOpencodePty: android→muslslot (CI swaps the android-built daemon into@opencode-ai/pty-linux-arm64-musl; the musl static binary runs on Android, but a rebuild is still needed for the TMPDIR socket fix);FFF_LIBC: android→"musl"(harmless: the linux branch is dead on android at runtime; fff itself is now disabled entirely, see 5).FFF_LIBCand compile-timeprocess.env.OPENTUI_LIBC: android→"musl". Never"android": the runtime loader throws for anything but unset/glibc/musl, so the first build's TUI was dead on arrival (--versiondoesn't touch the loader — that's why it looked fine).
- allow
v2-android.patch(guysoft's, applies cleanly to 0.5.14): bionic include handling,pthread/mlink fixes, miniaudio/Yoga translate shims.watcher-android.patch(packages/core/src/filesystem/watcher.ts):getBackend()had noandroidcase; mapandroid→inotify(@parcel/watcher-android-arm64uses inotify).opencode-pty-socket-tmpdir.patch(applied toanomalyco/opencode-pty, not the monorepo):socket_root()hardcoded/tmp, which is not writable on Android (ownedshell, mode0711). Respect absolute$TMPDIR(Termux sets$PREFIX/tmp); socket path still fitssun_path(~89 chars).patch-fff-embedded.py— REMOVED. It pointed fff's android branch at the official@ff-labs/fff-bin-android-arm64(pure bionic) because the musllibfff_c.sofaileddlopenon bionic. Removed after the bundled android lib proved to segfault the server while indexing some project trees; file finding uses the ripgrep fallback instead (OPENCODE_DISABLE_FFF=1in the wrapper), so no fff native ships at all.- Loader mapping (
scripts/patch-opentui-loader.py, anchor-based so chunk filename hashes don't matter): Bun's Android runtime reportsprocess.platform === "android", unknown to stock@opentui/core(Unsupported OpenTUI Node asset target: android-arm64). Map android →{linux, arm64, musl}and widen the linux branch. Verified byte-identical output against a hand patch.
opencode2 (wrapper, sh)
env: LD_LIBRARY_PATH=$PREFIX/lib (parcel watcher + rust_pty libc++_shared.so)
└─ opencode2.bin (bionic, Bun android base)
├─ JS/TS app + @parcel/watcher-android-arm64 + android→musl loader mapping
├─ EMBEDDED renderer: CI-built true-bionic libopentui.so
│ (swapped into the npm musl slot pre-build; NEEDED libm/libc/libdl)
├─ EMBEDDED pty daemon: CI-built bionic opencode-pty
│ (swapped into the pty-linux-arm64-musl npm slot; TMPDIR-patched)
└─ EMBEDDED inline pty: CI-built bionic librust_pty_arm64.so
(overwrites bun-pty's glibc .so pre-build; dlopen via bun:ffi)
No sidecar .so, no LD_PRELOAD, no OTUI_ASSET_ROOT, no patchelf in the
shipped packages: opencode2 + opencode2.bin only. (An earlier revision
used an OTUI_ASSET_ROOT override with a musl lib + 4-symbol shim; retired
once the CI bionic lib proved out.)
Watcher notes (patches/watcher-android.patch): upstream
getBackend() had no android case, so directory watches returned empty
even with the binding present. Map android→inotify. Verified on-device:
Node + Bun-android both subscribe/unsubscribe with inotify backend;
Bun needs LD_LIBRARY_PATH (official base has no RUNPATH to $PREFIX/lib,
unlike Termux-built node). Depends: libc++ provides libc++_shared.so.
File/dir file+entries watches already used node:fs and never needed
parcel; only recursive directory watches needed this fix. The old
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER wrapper var was stale (v2 reads
OPENCODE_FILEWATCHER_DISABLE/OPENCODE_DISABLE_FILEWATCHER) and is now
unset.
PTY notes: neither @opencode-ai/pty (0.2.0) nor bun-pty (0.4.9) ships
Android prebuilds (npm registry + greadelf: node-pty needs libc.so.6,
librust_pty_arm64.so needs libc.so.6). Actual errors, all fixed in-repo:
- Daemon dies at startup:
socket_root()hardcodes/tmp(unwritable on Android) →Permission denied (os error 13). The musl-static binary itself runs (--help/--versionOK). Fix:patches/opencode-pty-socket-tmpdir.patchrespects absolute$TMPDIR. Verified by executing the patched function:TMPDIRset → writable path fittingsun_path; unset → the sameEACCES. build.rshas noaarch64-linux-androidzig-target mapping (panics onunsupported Ghostty target), required for the Android cross-compile. Fix:patches/opencode-pty-buildrs-android.patch.rust-ptydepends onportable-pty 0.8, whosetermiosdep has no Android target (hard compile failure). 0.9 (viaserial2) builds unchanged. Fix:patches/bun-pty-portable09.patch. Verified: rebuilt.sois pure bionic and spawns shells with working IO under the Bun android runtime. Both natives are cargo+NDK cross-compiled in CI and vendored undervendor/like libopentui. Node-pty SEA path stays unused (Bun binary only).
.github/workflows/build-weekly.yml runs Mondays 03:00 UTC (plus manual):
resolve latest v2.* → skip if release exists → pin host Bun from upstream
packageManager + pty/bun-pty versions from packages/core/package.json →
verify Bun android base exists → clone/patch/install → patch loader →
build lib (Zig 0.16 + NDK r28) + build pty (cargo + NDK r28) → verify bionic
(no __errno_location, no libc.so.6) → package zip/deb/pacman + SHA256SUMS
→ release.
Known future breakage: opentui chunk rewrites (loader patch asserts anchors),
v2-android.patch drift (fails loudly), Bun dropping the android base asset
(pre-checked with a clear error).