fix(standalone): support explicit ld.so invocation via BUN_SELF_EXE#26753
fix(standalone): support explicit ld.so invocation via BUN_SELF_EXE#26753
Conversation
When a Bun compiled executable is invoked via explicit dynamic linker (e.g., /lib64/ld-linux-x86-64.so.2 ./my-app), /proc/self/exe points to the linker, not the actual binary. This causes the embedded bytecode detection to fail. Add BUN_SELF_EXE environment variable to override /proc/self/exe path detection, allowing compiled executables to work in Termux/Android and other environments requiring explicit ld.so invocation. Fixes #26752 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Code ReviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
WalkthroughAdds support for overriding the self-executable path via the BUN_SELF_EXE environment variable. This enables bundled executables to detect their embedded JavaScript when invoked through an explicit dynamic linker, addressing compatibility issues in environments like Termux/Android. Changes
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/StandaloneModuleGraph.zig`:
- Around line 1412-1418: The code opens the BUN_SELF_EXE path with
std.fs.cwd().openFile(), which uses the CWD and can fail for relative paths;
change this to resolve the BUN_SELF_EXE value to an absolute path (e.g., using
std.fs.path.resolve or equivalent) before opening and then call openFile on the
resolved path so that .fromStdFile receives a file handle for the absolute path;
update the logic surrounding bun.env_var.BUN_SELF_EXE.get(),
std.fs.cwd().openFile(path, .{}), and the .fromStdFile return path to use the
resolved absolute path.
In `@test/regression/issue/26752.test.ts`:
- Around line 10-17: Replace the single-file fixture write and the build
invocation that references "hello.js" by inlining the script via the "-e" flag:
remove creating "hello.js" in tempDir (variable dir) and change the Bun.spawn
call (compileProc) to call bunExe() with args ["build", "--compile", "-e",
'console.log("Hello from compiled Bun!");', "--outfile", String(dir) + "/hello"]
(or equivalent path assembly) while keeping cwd as String(dir); this keeps the
temp directory for the output binary but avoids the file fixture.
| // Check for override first (for Termux/explicit ld.so scenarios) | ||
| // When invoked via explicit dynamic linker (e.g., /lib64/ld-linux-x86-64.so.2 ./app), | ||
| // /proc/self/exe points to the linker, not the actual binary. | ||
| if (bun.env_var.BUN_SELF_EXE.get()) |path| { | ||
| if (std.fs.cwd().openFile(path, .{})) |file| { | ||
| return .fromStdFile(file); | ||
| } else |_| {} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/StandaloneModuleGraph.zig | sed -n '1405,1425p'Repository: oven-sh/bun
Length of output: 1160
🏁 Script executed:
# Check what BUN_SELF_EXE is and how it's used elsewhere
rg "BUN_SELF_EXE" --type zig -B 3 -A 3Repository: oven-sh/bun
Length of output: 1433
🏁 Script executed:
# Check for similar openFile patterns with environment variables in Zig files
rg "env_var.*\.get\(\)" src/ --type zig -B 2 -A 5Repository: oven-sh/bun
Length of output: 50367
🏁 Script executed:
# Verify absolute path usage patterns in this file
rg "openFileAbsolute|std\.fs\.path\.isAbsolute" src/StandaloneModuleGraph.zig -B 2 -A 2Repository: oven-sh/bun
Length of output: 720
🏁 Script executed:
# Check bun.path.joinAbsStringBuf usage for context and correctness
rg "bun\.path\.joinAbsStringBuf" src/ --type zig -B 1 -A 5 | head -60Repository: oven-sh/bun
Length of output: 3355
🏁 Script executed:
head -50 src/StandaloneModuleGraph.zig | grep -E "^(const|var|@import|pub)"Repository: oven-sh/bun
Length of output: 97
🏁 Script executed:
# Check imports and module structure
rg "^const|^var|@import" src/StandaloneModuleGraph.zig | head -30Repository: oven-sh/bun
Length of output: 466
🏁 Script executed:
# Verify std.fs.openFileAbsolute is standard in Zig
rg "std\.fs\.openFileAbsolute" src/ --type zig | head -20Repository: oven-sh/bun
Length of output: 1323
Use absolute paths when opening BUN_SELF_EXE.
The current code opens the path from BUN_SELF_EXE using std.fs.cwd().openFile(), which makes the behavior dependent on the current working directory. Since the path may be relative or absolute, resolve it to an absolute path first before opening, following the absolute-path requirement for file operations.
🔧 Proposed fix
- if (bun.env_var.BUN_SELF_EXE.get()) |path| {
- if (std.fs.cwd().openFile(path, .{})) |file| {
- return .fromStdFile(file);
- } else |_| {}
- }
+ if (bun.env_var.BUN_SELF_EXE.get()) |path| {
+ var abs_buf: bun.PathBuffer = undefined;
+ var cwd_buf: bun.PathBuffer = undefined;
+ const abs_path = if (std.fs.path.isAbsolute(path))
+ path
+ else
+ bun.path.joinAbsStringBuf(
+ bun.getcwd(&cwd_buf) catch path,
+ &abs_buf,
+ &.{path},
+ .auto,
+ );
+ if (std.fs.openFileAbsolute(abs_path, .{})) |file| {
+ return .fromStdFile(file);
+ } else |_| {}
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Check for override first (for Termux/explicit ld.so scenarios) | |
| // When invoked via explicit dynamic linker (e.g., /lib64/ld-linux-x86-64.so.2 ./app), | |
| // /proc/self/exe points to the linker, not the actual binary. | |
| if (bun.env_var.BUN_SELF_EXE.get()) |path| { | |
| if (std.fs.cwd().openFile(path, .{})) |file| { | |
| return .fromStdFile(file); | |
| } else |_| {} | |
| // Check for override first (for Termux/explicit ld.so scenarios) | |
| // When invoked via explicit dynamic linker (e.g., /lib64/ld-linux-x86-64.so.2 ./app), | |
| // /proc/self/exe points to the linker, not the actual binary. | |
| if (bun.env_var.BUN_SELF_EXE.get()) |path| { | |
| var abs_buf: bun.PathBuffer = undefined; | |
| var cwd_buf: bun.PathBuffer = undefined; | |
| const abs_path = if (std.fs.path.isAbsolute(path)) | |
| path | |
| else | |
| bun.path.joinAbsStringBuf( | |
| bun.getcwd(&cwd_buf) catch path, | |
| &abs_buf, | |
| &.{path}, | |
| .auto, | |
| ); | |
| if (std.fs.openFileAbsolute(abs_path, .{})) |file| { | |
| return .fromStdFile(file); | |
| } else |_| {} | |
| } |
🤖 Prompt for AI Agents
In `@src/StandaloneModuleGraph.zig` around lines 1412 - 1418, The code opens the
BUN_SELF_EXE path with std.fs.cwd().openFile(), which uses the CWD and can fail
for relative paths; change this to resolve the BUN_SELF_EXE value to an absolute
path (e.g., using std.fs.path.resolve or equivalent) before opening and then
call openFile on the resolved path so that .fromStdFile receives a file handle
for the absolute path; update the logic surrounding
bun.env_var.BUN_SELF_EXE.get(), std.fs.cwd().openFile(path, .{}), and the
.fromStdFile return path to use the resolved absolute path.
| using dir = tempDir("issue-26752", { | ||
| "hello.js": `console.log("Hello from compiled Bun!");`, | ||
| }); | ||
|
|
||
| // Compile the script into an executable | ||
| await using compileProc = Bun.spawn({ | ||
| cmd: [bunExe(), "build", "--compile", "hello.js", "--outfile", "hello"], | ||
| cwd: String(dir), |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Inline the single-file script with -e instead of writing hello.js.
This is a single-file test; inlining avoids the file fixture while keeping the temp directory for the output binary.
♻️ Suggested refactor
- using dir = tempDir("issue-26752", {
- "hello.js": `console.log("Hello from compiled Bun!");`,
- });
+ using dir = tempDir("issue-26752", {});
@@
- await using compileProc = Bun.spawn({
- cmd: [bunExe(), "build", "--compile", "hello.js", "--outfile", "hello"],
+ await using compileProc = Bun.spawn({
+ cmd: [bunExe(), "build", "--compile", "-e", `console.log("Hello from compiled Bun!");`, "--outfile", "hello"],As per coding guidelines: For single-file tests, prefer -e flag over tempDir when spawning Bun processes.
🤖 Prompt for AI Agents
In `@test/regression/issue/26752.test.ts` around lines 10 - 17, Replace the
single-file fixture write and the build invocation that references "hello.js" by
inlining the script via the "-e" flag: remove creating "hello.js" in tempDir
(variable dir) and change the Bun.spawn call (compileProc) to call bunExe() with
args ["build", "--compile", "-e", 'console.log("Hello from compiled Bun!");',
"--outfile", String(dir) + "/hello"] (or equivalent path assembly) while keeping
cwd as String(dir); this keeps the temp directory for the output binary but
avoids the file fixture.
Summary
BUN_SELF_EXEenvironment variable to override/proc/self/exepath detection for standalone executables/lib64/ld-linux-x86-64.so.2 ./my-app)Problem
When a Bun compiled executable is invoked via explicit dynamic linker, the kernel sets
/proc/self/exeto point to the linker instead of the actual binary. This causes:---- Bun! ----trailer at the endfromExecutable()returns nullThis affects:
Solution
Add
BUN_SELF_EXEenvironment variable that allows users to specify the correct path to the executable, which is checked before falling back to/proc/self/exe.Usage:
This follows the existing pattern used by
BUN_NEEDS_PROC_SELF_WORKAROUND.Test plan
test/regression/issue/26752.test.tsBUN_SELF_EXEoverride when invoked via explicit ld.soFixes #26752
🤖 Generated with Claude Code