diff --git a/src/tools/run-make-support/Cargo.toml b/src/tools/run-make-support/Cargo.toml
index d3605cd3dce05..cdc49d45c3d61 100644
--- a/src/tools/run-make-support/Cargo.toml
+++ b/src/tools/run-make-support/Cargo.toml
@@ -10,6 +10,6 @@ similar = "2.5.0"
 wasmparser = { version = "0.216", default-features = false, features = ["std"] }
 regex = "1.8" # 1.8 to avoid memchr 2.6.0, as 2.5.0 is pinned in the workspace
 gimli = "0.31.0"
+libc = "0.2"
 build_helper = { path = "../build_helper" }
 serde_json = "1.0"
-libc = "0.2"
diff --git a/src/tools/run-make-support/src/command.rs b/src/tools/run-make-support/src/command.rs
index 6b58173b34304..7d3e4ca845cd8 100644
--- a/src/tools/run-make-support/src/command.rs
+++ b/src/tools/run-make-support/src/command.rs
@@ -151,6 +151,48 @@ impl Command {
         self
     }
 
+    /// Set an auxiliary stream passed to the process, besides the stdio streams.
+    /// Use with caution - ideally, only set one aux fd; if there are multiple,
+    /// their old `fd` may overlap with another's `newfd`, and may break.
+    //FIXME: If more than 1 auxiliary file descriptor is needed, this function
+    // should be rewritten.
+    #[cfg(unix)]
+    pub fn set_aux_fd<F: Into<std::os::fd::OwnedFd>>(
+        &mut self,
+        newfd: std::os::fd::RawFd,
+        fd: F,
+    ) -> &mut Self {
+        use std::os::fd::AsRawFd;
+        use std::os::unix::process::CommandExt;
+
+        let cvt = |x| if x == -1 { Err(std::io::Error::last_os_error()) } else { Ok(()) };
+
+        let fd = fd.into();
+        if fd.as_raw_fd() == newfd {
+            // if the new file descriptor is already the same as fd, just turn off FD_CLOEXEC
+            // SAFETY: io-safe: fd is already owned.
+            cvt(unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, 0) })
+                .expect("disabling CLOEXEC failed");
+            // The pre_exec function should be unconditionally set, since it captures
+            // `fd`, and this ensures that it stays open until the fork
+        }
+        let pre_exec = move || {
+            if fd.as_raw_fd() != newfd {
+                // SAFETY: io-"safe": newfd is not necessarily an unused fd.
+                // However, we're ensuring that newfd will now refer to the same file descriptor
+                // as fd, which is safe as long as we manage the lifecycle of both descriptors
+                // correctly. This operation will replace the file descriptor referred to by newfd
+                // with the one from fd, allowing for shared access to the same underlying file or
+                // resource.
+                cvt(unsafe { libc::dup2(fd.as_raw_fd(), newfd) })?;
+            }
+            Ok(())
+        };
+        // SAFETY: dup2 is pre-exec-safe
+        unsafe { self.cmd.pre_exec(pre_exec) };
+        self
+    }
+
     /// Run the constructed command and assert that it is successfully run.
     ///
     /// By default, std{in,out,err} are [`Stdio::piped()`].
diff --git a/src/tools/run-make-support/src/macros.rs b/src/tools/run-make-support/src/macros.rs
index f7fe4f5422399..b2a14d8f881f8 100644
--- a/src/tools/run-make-support/src/macros.rs
+++ b/src/tools/run-make-support/src/macros.rs
@@ -80,6 +80,21 @@ macro_rules! impl_common_helpers {
                 self
             }
 
+            /// Set an auxiliary stream passed to the process, besides the stdio streams.
+            /// Use with caution - ideally, only set one aux fd; if there are multiple,
+            /// their old `fd` may overlap with another's `newfd`, and may break.
+            //FIXME: If more than 1 auxiliary file descriptor is needed, this function
+            // should be rewritten.
+            #[cfg(unix)]
+            pub fn set_aux_fd<F: Into<std::os::fd::OwnedFd>>(
+                &mut self,
+                newfd: std::os::fd::RawFd,
+                fd: F,
+            ) -> &mut Self {
+                self.cmd.set_aux_fd(newfd, fd);
+                self
+            }
+
             /// Run the constructed command and assert that it is successfully run.
             #[track_caller]
             pub fn run(&mut self) -> crate::command::CompletedProcess {
diff --git a/src/tools/tidy/src/allowed_run_make_makefiles.txt b/src/tools/tidy/src/allowed_run_make_makefiles.txt
index 50d21c7ed4c99..6534f42b965f5 100644
--- a/src/tools/tidy/src/allowed_run_make_makefiles.txt
+++ b/src/tools/tidy/src/allowed_run_make_makefiles.txt
@@ -4,7 +4,6 @@ run-make/emit-to-stdout/Makefile
 run-make/extern-fn-reachable/Makefile
 run-make/incr-add-rust-src-component/Makefile
 run-make/issue-84395-lto-embed-bitcode/Makefile
-run-make/jobserver-error/Makefile
 run-make/libs-through-symlinks/Makefile
 run-make/split-debuginfo/Makefile
 run-make/symbol-mangling-hashed/Makefile
diff --git a/tests/run-make/jobserver-error/Makefile b/tests/run-make/jobserver-error/Makefile
deleted file mode 100644
index 9f34970f96f10..0000000000000
--- a/tests/run-make/jobserver-error/Makefile
+++ /dev/null
@@ -1,17 +0,0 @@
-include ../tools.mk
-
-# only-linux
-# ignore-cross-compile
-
-# Test compiler behavior in case environment specifies wrong jobserver.
-# Note that by default, the compiler uses file descriptors 0 (stdin), 1 (stdout), 2 (stderr),
-# but also 3 and 4 for either end of the ctrl-c signal handler self-pipe.
-
-all:
-	bash -c 'echo "fn main() {}" | MAKEFLAGS="--jobserver-auth=5,5" $(RUSTC)' 2>&1 | diff cannot_open_fd.stderr -
-	bash -c 'echo "fn main() {}" | MAKEFLAGS="--jobserver-auth=3,3" $(RUSTC) - 3</dev/null' 2>&1 | diff not_a_pipe.stderr -
-
-# This test randomly fails, see https://github.com/rust-lang/rust/issues/110321
-disabled:
-	bash -c 'echo "fn main() {}" | MAKEFLAGS="--jobserver-auth=3,3" $(RUSTC) - 3< <(cat /dev/null)' 2>&1 | diff poisoned_pipe.stderr -
-
diff --git a/tests/run-make/jobserver-error/rmake.rs b/tests/run-make/jobserver-error/rmake.rs
new file mode 100644
index 0000000000000..e99a94e4ce514
--- /dev/null
+++ b/tests/run-make/jobserver-error/rmake.rs
@@ -0,0 +1,38 @@
+// If the environment variables contain an invalid `jobserver-auth`, this used
+// to cause an ICE (internal compiler error) until this was fixed in #109694.
+// Proper handling has been added, and this test checks that helpful warnings
+// and errors are printed instead in case of a wrong jobserver.
+// See https://github.com/rust-lang/rust/issues/46981
+
+// FIXME(Oneirical): only-linux ignore-cross-compile
+
+use run_make_support::{diff, rustc};
+
+fn main() {
+    let out = rustc()
+        .stdin_buf(("fn main() {}").as_bytes())
+        .env("MAKEFLAGS", "--jobserver-auth=5,5")
+        .run_fail()
+        .stderr_utf8();
+    diff().expected_file("cannot_open_fd.stderr").actual_text("actual", out).run();
+
+    let out = rustc()
+        .stdin_buf(("fn main() {}").as_bytes())
+        .input("-")
+        .env("MAKEFLAGS", "--jobserver-auth=3,3")
+        .set_aux_fd(3, std::fs::File::open("/dev/null").unwrap())
+        .run()
+        .stderr_utf8();
+    diff().expected_file("not_a_pipe.stderr").actual_text("actual", out).run();
+
+    // # this test randomly fails, see https://github.com/rust-lang/rust/issues/110321
+    // let (readpipe, _) = std::pipe::pipe().unwrap();
+    // let out = rustc()
+    //     .stdin("fn main() {}")
+    //     .input("-")
+    //     .env("MAKEFLAGS", "--jobserver-auth=3,3")
+    //     .set_fd3(readpipe)
+    //     .run()
+    //     .stderr_utf8();
+    // diff().expected_file("poisoned_pipe.stderr").actual_text("actual", out).run();
+}