diff --git a/cbits/posix/fork_exec.c b/cbits/posix/fork_exec.c
index dd6e58f1eecf4fcfeb787fb579816201b1386da2..3822955777ffdbb15e1f5fe0d47bef9e25a45ca4 100644
--- a/cbits/posix/fork_exec.c
+++ b/cbits/posix/fork_exec.c
@@ -101,6 +101,32 @@ setup_std_handle_fork(int fd,
     }
 }
 
+/* We must ensure that the fork communications pipe does not inhabit fds 0
+ * through 2 since we will need to manipulate these fds in
+ * setup_std_handle_fork while keeping the pipe available so that it can report
+ * errors. See #266.
+ */
+int unshadow_pipe_fd(int fd, char **failed_doing) {
+    if (fd <= 2) {
+        int fd2 = dup(fd);
+        if (fd2 == -1) {
+            *failed_doing = "dup(unshadow)";
+            return -1;
+        }
+
+        // This should recurse at most three times
+        int fd3 = unshadow_pipe_fd(fd2, failed_doing);
+        if (close(fd2) == -1) {
+            *failed_doing = "close(unshadow)";
+            return -1;
+        }
+
+        return fd3;
+    } else {
+        return fd;
+    }
+}
+
 /* Try spawning with fork. */
 ProcHandle
 do_spawn_fork (char *const args[],
@@ -119,6 +145,16 @@ do_spawn_fork (char *const args[],
         return -1;
     }
 
+    // Ensure that the pipe fds don't shadow stdin/stdout/stderr
+    forkCommunicationFds[0] = unshadow_pipe_fd(forkCommunicationFds[0], failed_doing);
+    if (forkCommunicationFds[0] == -1) {
+        return -1;
+    }
+    forkCommunicationFds[1] = unshadow_pipe_fd(forkCommunicationFds[1], failed_doing);
+    if (forkCommunicationFds[1] == -1) {
+        return -1;
+    }
+
     // Block signals with Haskell handlers.  The danger here is that
     // with the threaded RTS, a signal arrives in the child process,
     // the RTS writes the signal information into the pipe (which is