<#23372 Remote AC writes drop `output_directories`...
# github-notifications
q
#23372 Remote AC writes drop `output_directories` for REv2.0 root-capture (`output_directories=[""]`), poisoning the cache for downstream consumers Issue created by sbarnat 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's
Command.output_directories
contains
""
(root-capture). 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 1. Define a Pants process that declares
output_directories=[""]
(REv2.0 root capture). 2. Configure
[GLOBAL]
remote_cache_write = true
against any REv2 server that exposes a working
ActionCache
write path (we used Buildbarn
bb-storage:20250827T121715Z
). 3. Flush the AC so a fresh write happens. 4. Run the goal that exercises the process. Pants reports success; the worker
ExecuteResponse
includes a proper
outputDirectories: [{ treeDigest: <2824-byte Tree of XVM/...> }]
. 5. 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. pantsbuild/pants