Skip to content

Commit 0807153

Browse files
rejuvenileclaude
andcommitted
Resolve absolute symlinks in output upload instead of uploading raw targets
When DirectoryCache creates the work directory, output directories like _bs.cargo_runfiles may be symlinks pointing into the cache directory (/Users/.../directory_cache/...). The output collection code uploaded these as raw symlinks with absolute targets that are meaningless on the Bazel client, causing "No such file or directory" errors. Fix: detect absolute symlinks in output paths and resolve them — upload directory contents as Tree protos and file contents as regular files. Only preserve relative symlinks (intentionally created by the action). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5e87ee3 commit 0807153

1 file changed

Lines changed: 101 additions & 24 deletions

File tree

nativelink-worker/src/running_actions_manager.rs

Lines changed: 101 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2839,34 +2839,111 @@ impl RunningActionImpl {
28392839
.err_tip(|| format!("Uploading directory {}", full_path.display()))?,
28402840
))
28412841
} else if metadata.is_symlink() {
2842-
let output_symlink = upload_symlink(&full_path, work_directory)
2842+
// Resolve the symlink to determine what it points to.
2843+
// Symlinks created by DirectoryCache (absolute paths into
2844+
// the cache directory) must NOT be uploaded as symlinks —
2845+
// the target path is worker-local and meaningless to the
2846+
// client. Instead, follow the symlink and upload the
2847+
// resolved content (file or directory).
2848+
let target = fs::read_link(&full_path)
28432849
.await
2844-
.map(|mut symlink_info| {
2845-
symlink_info.name_or_path = NameOrPath::Path(entry);
2846-
symlink_info
2847-
})
2848-
.err_tip(|| format!("Uploading symlink {}", full_path.display()))?;
2849-
match fs::metadata(&full_path).await {
2850-
Ok(metadata) => {
2851-
if metadata.is_dir() {
2852-
Ok(OutputType::DirectorySymlink(output_symlink))
2853-
} else {
2854-
// Note: If it's anything but directory we put it as a file symlink.
2855-
Ok(OutputType::FileSymlink(output_symlink))
2850+
.err_tip(|| format!("Reading symlink target for {}", full_path.display()))?;
2851+
let is_absolute_symlink = Path::new(&target).is_absolute();
2852+
2853+
if is_absolute_symlink {
2854+
// Absolute symlink — resolve and upload contents.
2855+
match fs::metadata(&full_path).await {
2856+
Ok(resolved_meta) => {
2857+
if resolved_meta.is_dir() {
2858+
// Upload as directory (Tree proto).
2859+
Ok(OutputType::Directory(
2860+
upload_directory(
2861+
cas_store.as_pin(),
2862+
&full_path,
2863+
work_directory,
2864+
hasher,
2865+
digest_uploaders,
2866+
)
2867+
.and_then(|(root_dir, children)| async move {
2868+
let tree = ProtoTree {
2869+
root: Some(root_dir),
2870+
children: children.into(),
2871+
};
2872+
let tree_digest = serialize_and_upload_message(
2873+
&tree,
2874+
cas_store.as_pin(),
2875+
&mut hasher.hasher(),
2876+
)
2877+
.await
2878+
.err_tip(|| format!("While processing {entry}"))?;
2879+
Ok(DirectoryInfo {
2880+
path: entry,
2881+
tree_digest,
2882+
})
2883+
})
2884+
.await
2885+
.err_tip(|| format!("Uploading symlinked directory {}", full_path.display()))?,
2886+
))
2887+
} else {
2888+
// Upload as file (follow symlink).
2889+
Ok(OutputType::File(
2890+
upload_file(
2891+
cas_store.as_pin(),
2892+
&full_path,
2893+
hasher,
2894+
resolved_meta,
2895+
digest_uploaders,
2896+
)
2897+
.await
2898+
.map(|mut file_info| {
2899+
file_info.name_or_path = NameOrPath::Path(entry);
2900+
file_info
2901+
})
2902+
.err_tip(|| format!("Uploading symlinked file {}", full_path.display()))?,
2903+
))
2904+
}
2905+
}
2906+
Err(e) => {
2907+
if e.code != Code::NotFound {
2908+
return Err(e).err_tip(|| {
2909+
format!(
2910+
"While resolving absolute symlink {}",
2911+
full_path.display()
2912+
)
2913+
});
2914+
}
2915+
Ok(OutputType::None)
28562916
}
28572917
}
2858-
Err(e) => {
2859-
if e.code != Code::NotFound {
2860-
return Err(e).err_tip(|| {
2861-
format!(
2862-
"While querying target symlink metadata for {}",
2863-
full_path.display()
2864-
)
2865-
});
2918+
} else {
2919+
// Relative symlink — action intentionally created it.
2920+
// Upload as a proper symlink.
2921+
let output_symlink = upload_symlink(&full_path, work_directory)
2922+
.await
2923+
.map(|mut symlink_info| {
2924+
symlink_info.name_or_path = NameOrPath::Path(entry);
2925+
symlink_info
2926+
})
2927+
.err_tip(|| format!("Uploading symlink {}", full_path.display()))?;
2928+
match fs::metadata(&full_path).await {
2929+
Ok(metadata) => {
2930+
if metadata.is_dir() {
2931+
Ok(OutputType::DirectorySymlink(output_symlink))
2932+
} else {
2933+
Ok(OutputType::FileSymlink(output_symlink))
2934+
}
2935+
}
2936+
Err(e) => {
2937+
if e.code != Code::NotFound {
2938+
return Err(e).err_tip(|| {
2939+
format!(
2940+
"While querying target symlink metadata for {}",
2941+
full_path.display()
2942+
)
2943+
});
2944+
}
2945+
Ok(OutputType::FileSymlink(output_symlink))
28662946
}
2867-
// If the file doesn't exist, we consider it a file. Even though the
2868-
// file doesn't exist we still need to populate an entry.
2869-
Ok(OutputType::FileSymlink(output_symlink))
28702947
}
28712948
}
28722949
} else {

0 commit comments

Comments
 (0)