From 16c3c6d44c01051be52390e57eba810e2c64df69 Mon Sep 17 00:00:00 2001 From: zbarsky-openai Date: Mon, 24 Aug 2026 08:34:50 -0400 Subject: [PATCH] executor: expose file descriptors inside action sandboxes Bash process substitutions fail inside action sandboxes because the normal `/dev/fd` descriptor link is absent: ```text join: /dev/fd/63: No such file or directory sort: cannot read: /dev/fd/62: No such file or directory ``` This occurs in real remote `UnusedTarInputs` actions while computing OCI-layer dependencies. Some actions continue after the error, leaving incorrect or empty unused-input sets. Create the standard `/dev/fd -> /proc/self/fd` symlink in each action's existing device directory. The action already mounts its own procfs after entering its mount namespace, so the link remains local to the sandbox and requires no runtime packages or additional mounts. Extend the existing base directory test to assert the exact symlink target. The failure was reproduced in all four remote OCI-layer rule tests with Bazel `9.3.0-actiond-dzbarsky14` and actiond invocation `e04aa24e-0f40-4715-92b9-1afe76b48268`. Running the upstream unit suite locally is currently blocked by the repository's rejected BuildBuddy API key. --- src/action_executor.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/action_executor.zig b/src/action_executor.zig index 29a208f..246ba7f 100644 --- a/src/action_executor.zig +++ b/src/action_executor.zig @@ -2110,6 +2110,7 @@ fn createOutputParent(io: std.Io, work_root: std.Io.Dir, path: []const u8) !void fn prepareChrootBaseDirs(io: std.Io, chroot_root: std.Io.Dir) !void { try chroot_root.createDirPath(io, "dev"); + try chroot_root.symLink(io, "/proc/self/fd", "dev/fd", .{ .is_directory = true }); try chroot_root.createDirPath(io, "proc"); try chroot_root.createDirPath(io, "tmp"); try chroot_root.createDirPath(io, "var/tmp"); @@ -3641,6 +3642,9 @@ test "prepareChrootBaseDirs creates temporary directories" { defer work_dir.close(std.testing.io); try prepareChrootBaseDirs(std.testing.io, work_dir); + var descriptor_target: [64]u8 = undefined; + const descriptor_target_len = try work_dir.readLink(std.testing.io, "dev/fd", &descriptor_target); + try std.testing.expectEqualStrings("/proc/self/fd", descriptor_target[0..descriptor_target_len]); try work_dir.access(std.testing.io, "tmp", .{}); try work_dir.access(std.testing.io, "var/tmp", .{}); }