diff --git a/src/common.cpp b/src/common.cpp
index 545288fc0..b341f226f 100644
--- a/src/common.cpp
+++ b/src/common.cpp
@@ -67,6 +67,7 @@ bool g_profiling_active = false;
 const wchar_t *program_name;
 
 int debug_level=1;
+bool has_working_tty_timestamps = true;
 
 /**
    This struct maintains the current state of the terminal size. It is updated on demand after receiving a SIGWINCH.
diff --git a/src/common.h b/src/common.h
index d6eb01910..18b71cb64 100644
--- a/src/common.h
+++ b/src/common.h
@@ -25,6 +25,13 @@
 #include "config.h"
 #include "fallback.h"
 
+// Define a symbol we can use elsewhere in our code to determine if we're being built on MS Windows
+// under Cygwin.
+#if defined(_WIN32) || defined(_WIN64) || defined(WIN32) || defined(__CYGWIN__) || \
+    defined(__WIN32__)
+#define OS_IS_CYGWIN
+#endif
+
 /**
    Avoid writing the type name twice in a common "static_cast-initialization".
    Caveat: This doesn't work with type names containing commas!
@@ -196,6 +203,10 @@ extern const wchar_t *program_name;
 void read_ignore(int fd, void *buff, size_t count);
 void write_ignore(int fd, const void *buff, size_t count);
 
+/// Set to false at run-time if it's been determined we can't trust the last modified timestamp on
+/// the tty.
+extern bool has_working_tty_timestamps;
+
 /**
    This macro is used to check that an input argument is not null. It
    is a bit lika a non-fatal form of assert. Instead of exit-ing on
diff --git a/src/fish.cpp b/src/fish.cpp
index 9676814de..39c9d5eb2 100644
--- a/src/fish.cpp
+++ b/src/fish.cpp
@@ -468,6 +468,36 @@ static int fish_parse_opt(int argc, char **argv, std::vector<std::string> *cmds)
     return optind;
 }
 
+/// Various things we need to initialize at run-time that don't really fit any of the other init
+/// routines.
+static void misc_init() {
+#ifdef OS_IS_CYGWIN
+    // MS Windows tty devices do not have either a read or write timestamp. Those respective fields
+    // of `struct stat` are always the current time. Which means we can't use them. So we assume no
+    // external program has written to the terminal behind our back. This makes multiline prompts
+    // usable. See issue #2859.
+    has_working_tty_timestamps = false;
+#else
+    // This covers Windows Subsystem for Linux (WSL).
+    int fd = open("/proc/sys/kernel/osrelease", O_RDONLY);
+    if (fd != -1) {
+        const char *magic_suffix = "Microsoft";
+        int len_magic_suffix = strlen(magic_suffix);
+        char osrelease[128] = {0};
+        int len_osrelease = read(fd, osrelease, sizeof(osrelease) - 1);
+        if (osrelease[len_osrelease - 1] == '\n') {
+            osrelease[len_osrelease - 1] = '\0';
+            len_osrelease--;
+        }
+        if (len_osrelease >= len_magic_suffix &&
+            strcmp(magic_suffix, osrelease + len_osrelease - len_magic_suffix) == 0) {
+            has_working_tty_timestamps = false;
+        }
+        close(fd);
+    }
+#endif  // OS_IS_MS_WINDOWS
+}
+
 int main(int argc, char **argv)
 {
     int res=1;
@@ -519,6 +549,7 @@ int main(int argc, char **argv)
     history_init();
     /* For setcolor to support term256 in config.fish (#1022) */
     update_fish_color_support();
+    misc_init();
 
     parser_t &parser = parser_t::principal_parser();
 
diff --git a/src/screen.cpp b/src/screen.cpp
index 7bb5cf4bd..36123c2da 100644
--- a/src/screen.cpp
+++ b/src/screen.cpp
@@ -459,12 +459,18 @@ static void s_check_status(screen_t *s)
 {
     fflush(stdout);
     fflush(stderr);
+    if (!has_working_tty_timestamps) {
+        // We can't reliably determine if the terminal has been written to behind our back so we
+        // just assume that hasn't happened and hope for the best. This is important for multi-line
+        // prompts to work correctly.
+        return;
+    }
 
     fstat(1, &s->post_buff_1);
     fstat(2, &s->post_buff_2);
 
-    int changed = (s->prev_buff_1.st_mtime != s->post_buff_1.st_mtime) ||
-                  (s->prev_buff_2.st_mtime != s->post_buff_2.st_mtime);
+    bool changed = (s->prev_buff_1.st_mtime != s->post_buff_1.st_mtime) ||
+                   (s->prev_buff_2.st_mtime != s->post_buff_2.st_mtime);
 
     #if defined HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
         changed = changed || s->prev_buff_1.st_mtimespec.tv_nsec != s->post_buff_1.st_mtimespec.tv_nsec ||