From af64a52083ff92d0def54a4be24f53a06c447bb1 Mon Sep 17 00:00:00 2001 From: LeaCoder0 <19y104@gmail.com> Date: Thu, 7 May 2026 12:08:58 +0530 Subject: [PATCH] fix(cli): redirect Bun's tempdir when /tmp is mounted noexec On hardened Linux systems (CIS / STIG / Lynis baselines mount /tmp with noexec), Bun's extracted libopentui.so cannot be dlopen()ed and the TUI hangs silently with no visible error unless --print-logs --log-level DEBUG is used. Detect a noexec mount covering $TMPDIR (default /tmp) by parsing /proc/self/mounts and, if found, redirect Bun's extraction via BUN_TMPDIR to ~/.cache/opencode/tmp. The check is Linux-only, a no-op when /tmp is exec-allowed, and respects an existing user-supplied BUN_TMPDIR. Refs #5175, #3765, #4605, #6080. --- packages/opencode/bin/opencode | 49 ++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/opencode/bin/opencode b/packages/opencode/bin/opencode index a7674ce2f875..bceecbea84d5 100755 --- a/packages/opencode/bin/opencode +++ b/packages/opencode/bin/opencode @@ -17,6 +17,55 @@ function run(target) { process.exit(code) } +// Linux-only workaround for noexec /tmp. +// On hardened Linux (CIS / STIG / Lynis baselines mount /tmp with noexec), +// dlopen() of Bun's extracted libopentui.so fails with "failed to map +// segment from shared object" and the TUI hangs silently. Detect a noexec +// mount covering the temp dir and redirect Bun's extraction to a writable, +// exec-allowed dir under the user's home. +// Refs: #5175, #3765, #4605, #6080 +function ensureExecutableTmpdir() { + if (process.platform !== "linux") return + if (process.env.BUN_TMPDIR) return + + const candidate = process.env.TMPDIR || "/tmp" + let mounts + try { + mounts = fs.readFileSync("/proc/self/mounts", "utf8") + } catch { + return + } + + let best = null + for (const line of mounts.split("\n")) { + const parts = line.split(/\s+/) + if (parts.length < 4) continue + const mountPoint = parts[1] + const options = parts[3] + if ( + candidate === mountPoint || + candidate.startsWith(mountPoint === "/" ? "/" : mountPoint + "/") + ) { + if (!best || mountPoint.length > best.mountPoint.length) { + best = { mountPoint, options } + } + } + } + + if (!best) return + if (!best.options.split(",").includes("noexec")) return + + const fallback = path.join(os.homedir(), ".cache", "opencode", "tmp") + try { + fs.mkdirSync(fallback, { recursive: true }) + process.env.BUN_TMPDIR = fallback + } catch { + // best effort; if we can't create the fallback, the original error surfaces + } +} + +ensureExecutableTmpdir() + const envPath = process.env.OPENCODE_BIN_PATH if (envPath) { run(envPath)