Description of the bug
When a Starlark rule creates a directory symlink using ctx.actions.symlink(output, target_path=relative_dir), the rlocation function in runfiles.bash fails to resolve the symlink path, returning an empty string.
The symlink exists and works correctly in the runfiles directory. The issue is that runfiles.bash initialization prefers the manifest, and the manifest cannot properly represent symlinks with relative targets.
High level symptom
- Rule creates symlink:
ctx.actions.symlink(output=link, target_path="target_dir")
- Manifest stores:
path/to/link target_dir (relative path)
rlocation("path/to/link") looks up manifest, gets target_dir
- Checks if
target_dir exists as file - it doesn't (relative path)
- Returns empty string
Steps to reproduce
OR if you'd like to manually run it
- Extract runfiles-relative-symlink.tar.gz
- Run:
bazel run //pkg:test
- Observe the error output showing
rlocation returns empty for the symlink
Note: The test must be in a non-root package (pkg/) to reproduce. In the root package, the bug is masked because the relative path _subdir_link_dir happens to exist from $RUNFILES/_main/. In nested packages, it's at $RUNFILES/_main/pkg/_subdir_link_dir but rlocation still returns just _subdir_link_dir.
Root Cause
In runfiles.bash lines 88-96, initialization only sets ONE of RUNFILES_DIR or RUNFILES_MANIFEST_FILE:
if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
if [[ -f "$0.runfiles_manifest" ]]; then
export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest" # Wins, RUNFILES_DIR never set
elif [[ -f "$0.runfiles/MANIFEST" ]]; then
export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
export RUNFILES_DIR="$0.runfiles"
fi
fi
Since $0.runfiles_manifest typically exists, the manifest is used and RUNFILES_DIR is never set.
Then in runfiles_rlocation_checked (line 351):
if [[ -e "${RUNFILES_DIR:-/dev/null}/$1" ]]; then # Always false: RUNFILES_DIR not set
Proposed Fix
Set BOTH variables when both the runfiles directory and manifest exist.
This ensures rlocation can check the directory first (where symlinks work) before falling back to manifest.
Workaround
Add this after the runfiles.bash initialization block
Operating System
$ cat /etc/os-release
NAME="AlmaLinux"
VERSION="9.7 (Moss Jungle Cat)"
ID="almalinux"
VERSION_ID="9.7"
PLATFORM_ID="platform:el9"
Bazel Version
$ bazel info release
release 8.5.1
This also happens in Bazel 9.0
Additional Context
Debug output from RUNFILES_LIB_DEBUG=1:
INFO[runfiles.bash]: rlocation(_main/path/_subdir_link): looking in RUNFILES_MANIFEST_FILE
INFO[runfiles.bash]: rlocation(_main/path/_subdir_link): found in manifest as (_subdir_link_dir), but file does not exist
The manifest correctly maps the symlink, but the relative target path _subdir_link_dir cannot be resolved as a standalone path.
Description of the bug
When a Starlark rule creates a directory symlink using
ctx.actions.symlink(output, target_path=relative_dir), therlocationfunction inrunfiles.bashfails to resolve the symlink path, returning an empty string.The symlink exists and works correctly in the runfiles directory. The issue is that
runfiles.bashinitialization prefers the manifest, and the manifest cannot properly represent symlinks with relative targets.High level symptom
ctx.actions.symlink(output=link, target_path="target_dir")path/to/link target_dir(relative path)rlocation("path/to/link")looks up manifest, getstarget_dirtarget_direxists as file - it doesn't (relative path)Steps to reproduce
https://github.com/wade-arista/bazel-demo/actions/runs/21880813798/job/63162467607
OR if you'd like to manually run it
bazel run //pkg:testrlocationreturns empty for the symlinkNote: The test must be in a non-root package (
pkg/) to reproduce. In the root package, the bug is masked because the relative path_subdir_link_dirhappens to exist from$RUNFILES/_main/. In nested packages, it's at$RUNFILES/_main/pkg/_subdir_link_dirbut rlocation still returns just_subdir_link_dir.Root Cause
In
runfiles.bashlines 88-96, initialization only sets ONE ofRUNFILES_DIRorRUNFILES_MANIFEST_FILE:Since
$0.runfiles_manifesttypically exists, the manifest is used andRUNFILES_DIRis never set.Then in
runfiles_rlocation_checked(line 351):Proposed Fix
Set BOTH variables when both the runfiles directory and manifest exist.
This ensures
rlocationcan check the directory first (where symlinks work) before falling back to manifest.Workaround
Add this after the runfiles.bash initialization block
Operating System
Bazel Version
This also happens in Bazel 9.0
Additional Context
Debug output from
RUNFILES_LIB_DEBUG=1:The manifest correctly maps the symlink, but the relative target path
_subdir_link_dircannot be resolved as a standalone path.