Skip to content

fix(runfiles): resolve real exe path when argv[0] is relative (fixes bazel run)#44

Closed
gregmagolan wants to merge 2 commits into
hermeticbuild:mainfrom
gregmagolan:test/relative-argv0-runfiles-discovery
Closed

fix(runfiles): resolve real exe path when argv[0] is relative (fixes bazel run)#44
gregmagolan wants to merge 2 commits into
hermeticbuild:mainfrom
gregmagolan:test/relative-argv0-runfiles-discovery

Conversation

@gregmagolan

@gregmagolan gregmagolan commented Jun 16, 2026

Copy link
Copy Markdown

Summary

A launcher binary aborts with ERROR: execve failed with errno 2 when exec'd with a relative argv[0] from a working directory unrelated to the binary, with no RUNFILES_DIR set. This is exactly what bazel run does: it sets cwd to <x>.runfiles/_main, passes argv[0] = "bazel-bin/<name>", and leaves RUNFILES_DIR unset.

In that case the launcher computes <argv[0]>.runfiles relative to cwd<x>.runfiles/_main/bazel-bin/<name>.runfiles, which doesn't exist → runfiles discovery returns None → the embedded program rlocation is left unresolved → execve of a bad path → errno 2.

Reported downstream as aspect-build/rules_py#1113: an aspect_rules_py py_binary fails under bazel run. (Confirmed it reproduces on this repo's main with the new test, and that pinning rules_py to the latest released launcher did not fix it — the bug is in the launcher, not the version.)

Fix

When argv[0] is not absolute, resolve the real executable path and use it for <exe>.runfiles discovery instead of joining a relative argv[0] against cwd:

  • macOS: _NSGetExecutablePath
  • Linux (x86_64 / aarch64): readlinkat(/proc/self/exe) (adds SYS_READLINKAT + a syscall4 macro for x86_64)
  • s390x / Windows: unchanged — return None, falling back to the existing argv[0] behavior (Windows derives argv[0] from the command line via CreateProcessW, so it doesn't hit this path; s390x readlinkat is left unwired)

RUNFILES_DIR / RUNFILES_MANIFEST_FILE still take precedence when set.

Tests

Adds two integration cases:

  • relative_argv0_discovery — reproduces bazel run's geometry exactly (relative argv[0] via an arg0 override, cwd inside the runfiles tree, no runfiles env vars).
  • symlinked_program_relative_argv0 — same, with the embedded program reached through a relative symlink (the venv shape rules_py uses).

Both fail before this change and pass after.

Test plan

  • bazel test //integration-tests:integration_test — 11/11 pass on macOS with the fix; relative_argv0_discovery fails without it.
  • ⚠️ The Linux readlinkat paths were not run locally (macOS host) — please confirm on CI. The macOS path is verified end-to-end.

🤖 Generated with Claude Code

…ILES_DIR)

Add a regression test that invokes the launcher via a RELATIVE argv[0] from a
different working directory with no RUNFILES_DIR / RUNFILES_MANIFEST_FILE — the
`bazel run` / deployed-binary scenario. The launcher must discover
`<argv[0]>.runfiles` and resolve an absolute program path for execve, so the
exec succeeds regardless of the caller's cwd.

The existing `fallback_runfiles_dir` test only invokes the stub via an absolute
argv[0], which doesn't exercise this path. Downstream this surfaced as
"execve failed with errno 2" when running an aspect_rules_py py_binary via
`bazel run` (aspect-build/rules_py#1113) with hermetic_launcher 0.0.9; it
passes on current main, so this locks in the behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@malt3

malt3 commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Error in download_and_extract: java.io.IOException: io.grpc.StatusRuntimeException: UNAUTHENTICATED: User not found

That's on me. Let me fix the rbe credential handling for forks.

…] is relative

When the launcher is exec'd with a relative argv[0] from a cwd unrelated to the
binary (notably `bazel run`, which sets cwd inside `<x>.runfiles/_main`, passes
argv[0]="bazel-bin/<name>", and does NOT set RUNFILES_DIR), `<argv[0]>.runfiles`
resolved against cwd points nowhere. Discovery then fails, the embedded program
rlocation is left unresolved, and execve aborts with errno 2 — observed
downstream as aspect-build/rules_py#1113 ("execve failed with errno 2") when
running a py_binary via `bazel run`.

Fix: when argv[0] is not absolute, resolve the real executable path and use it
for `<exe>.runfiles` discovery:
- macOS: _NSGetExecutablePath
- Linux (x86_64/aarch64): readlinkat(/proc/self/exe)
- other (s390x/Windows): unchanged (return None -> argv[0] fallback)

RUNFILES_DIR / RUNFILES_MANIFEST_FILE still take precedence when set.

Adds an integration case `relative_argv0_discovery` reproducing `bazel run`'s
geometry exactly (relative argv[0] via arg0 override + cwd inside runfiles + no
runfiles env), plus `symlinked_program_relative_argv0` for the venv-shaped
relative-symlink program target. Both fail before this change and pass after.

NB: the Linux readlinkat paths could not be run locally (macOS host); the macOS
path is verified end-to-end. s390x readlinkat is intentionally left unwired.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@malt3

malt3 commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

@gregmagolan please rebase onto the latest HEAD of main (ad4500d). It includes #46.

@gregmagolan gregmagolan changed the title test(integration): cover relative argv[0] runfiles discovery (no RUNFILES_DIR) fix(runfiles): resolve real exe path when argv[0] is relative (fixes bazel run) Jun 16, 2026
@malt3

malt3 commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Superseded by #48

@malt3 malt3 closed this Jun 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants