Describe the bug
Pants's remote_cache::CommandRunner::update_action_cache silently drops output_directories from the ActionResult it writes via UpdateActionResult whenever the action declares a root-capture output — i.e. Process(..., output_directories=["."]) on the client side, which Pants normalizes to output_directories=[""] on the REv2 wire. The resulting AC entry has exit_code: 0, empty output_directories, and passes completeness-checking on the BB side because there are no referenced blobs left to validate. Downstream consumers receive a "successful" cache hit with no outputs, which silently merges in nothing and breaks the build several steps later with no usable error message.
Pants version
2.32.0
OS
Linux, but the issue is not OS dependent
Additional info
Reproduction
- Define a Pants process that declares
output_directories=[""] (REv2.0 root capture).
- Configure
[GLOBAL] remote_cache_write = true against any REv2 server that exposes a working ActionCache write path (we used Buildbarn bb-storage:20250827T121715Z).
- Flush the AC so a fresh write happens.
- Run the goal that exercises the process. Pants reports success; the worker
ExecuteResponse includes a proper outputDirectories: [{ treeDigest: <2824-byte Tree of XVM/...> }].
- Query the AC entry Pants just wrote:
# Synthetic example using REv2 Python proto bindings.
res = ac_stub.GetActionResult(GetActionResultRequest(
instance_name="fuse",
action_digest=Digest(hash="<the action digest from worker logs>", size_bytes=141),
))
print(res.output_directories) # -> [] (BUG: should contain the root tree)
print(res.execution_metadata.worker_completed_timestamp)
# -> seconds: 1, nanos: ~3e8 (also wrong, but cosmetic)
##Root Cause
make_tree_for_output_directory in src/rust/process_execution/remote/src/remote_cache.rs is:
pub(crate) fn make_tree_for_output_directory(
root_trie: &DigestTrie,
directory_path: RelativePath,
) -> Result<Option<(Tree, Vec<Digest>)>, String> {
let sub_trie = match root_trie.entry(&directory_path)? {
None => return Ok(None), // <-- triggered for empty path
Some(directory::Entry::Directory(d)) => d.tree(),
...
};
...
}
When directory_path is empty (e.g. from Command.output_directories = [""]), root_trie.entry(&path) iterates zero path components and returns Ok(None) (see DigestTrie::entry_helper in fs/src/directory.rs). The caller in make_action_result then hits its None => continue arm and silently drops the output:
for output_directory in &command.output_directories {
let (tree, file_digests) = match Self::make_tree_for_output_directory(...)? {
Some(res) => res,
None => continue, // <-- silently skips output_directories=[""]
};
...
}
The None => continue is intentional per #11772 (REAPI says missing declared outputs should be skipped, not errored). It just never accounted for the legitimate root-capture case where the "path" is the root itself.
Describe the bug
Pants's
remote_cache::CommandRunner::update_action_cachesilently dropsoutput_directoriesfrom theActionResultit writes viaUpdateActionResultwhenever the action declares a root-capture output — i.e.Process(..., output_directories=["."])on the client side, which Pants normalizes tooutput_directories=[""]on the REv2 wire. The resulting AC entry hasexit_code: 0, emptyoutput_directories, and passes completeness-checking on the BB side because there are no referenced blobs left to validate. Downstream consumers receive a "successful" cache hit with no outputs, which silently merges in nothing and breaks the build several steps later with no usable error message.Pants version
2.32.0
OS
Linux, but the issue is not OS dependent
Additional info
Reproduction
output_directories=[""](REv2.0 root capture).[GLOBAL]remote_cache_write = trueagainst any REv2 server that exposes a workingActionCachewrite path (we used Buildbarnbb-storage:20250827T121715Z).ExecuteResponseincludes a properoutputDirectories: [{ treeDigest: <2824-byte Tree of XVM/...> }].