diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 73008a313c0..c3d62e0f85e 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1426,10 +1426,11 @@ linux_nat_target::detach (inferior *inf, int from_tty)
 
   iterate_over_lwps (ptid_t (pid), detach_callback);
 
-  /* Only the initial process should be left right now.  */
-  gdb_assert (num_lwps (pid) == 1);
-
-  main_lwp = find_lwp_pid (ptid_t (pid));
+  /* We have detached from everything except the main thread now, so
+     should only have one thread left.  However, in non-stop mode the
+     main thread might have exited, in which case we'll have no threads
+     left.  */
+  gdb_assert (num_lwps (pid) <= 1);
 
   if (forks_exist_p ())
     {
@@ -1443,10 +1444,14 @@ linux_nat_target::detach (inferior *inf, int from_tty)
     {
       target_announce_detach (from_tty);
 
-      /* Pass on any pending signal for the last LWP.  */
-      int signo = get_detach_signal (main_lwp);
+      main_lwp = find_lwp_pid (ptid_t (pid));
+      if (main_lwp != nullptr)
+	{
+	  /* Pass on any pending signal for the last LWP.  */
+	  int signo = get_detach_signal (main_lwp);
 
-      detach_one_lwp (main_lwp, &signo);
+	  detach_one_lwp (main_lwp, &signo);
+	}
 
       detach_success (inf);
     }
diff --git a/gdb/remote.c b/gdb/remote.c
index ff3d7e5cd32..f61035a26b0 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -6126,7 +6126,24 @@ remote_target::remote_detach_pid (int pid)
   else if (rs->buf[0] == '\0')
     error (_("Remote doesn't know how to detach"));
   else
-    error (_("Can't detach process."));
+    {
+      /* It is possible that we have an unprocessed exit event for this
+	 pid.  If this is the case then we can ignore the failure to detach
+	 and just pretend that the detach worked, as far as the user is
+	 concerned, the process exited immediately after the detach.  */
+      bool process_has_already_exited = false;
+      struct remote_notif_state *rns = rs->notif_state;
+      auto reply
+	= (struct stop_reply *) rns->pending_event[notif_client_stop.id];
+      if (reply != nullptr && reply->ptid.pid () == pid)
+	{
+	  if (reply->ws.kind () == TARGET_WAITKIND_EXITED)
+	    process_has_already_exited = true;
+	}
+
+      if (!process_has_already_exited)
+	error (_("Can't detach process: %s"), (char *) rs->buf.data ());
+    }
 }
 
 /* This detaches a program to which we previously attached, using
diff --git a/gdb/testsuite/gdb.threads/main-thread-exit-during-detach.c b/gdb/testsuite/gdb.threads/main-thread-exit-during-detach.c
new file mode 100644
index 00000000000..ecd0138c354
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/main-thread-exit-during-detach.c
@@ -0,0 +1,59 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <pthread.h>
+#include <unistd.h>
+
+/* This is set from GDB to allow the main thread to exit.  */
+
+volatile int dont_exit_just_yet = 1;
+
+/* Somewhere to place a breakpoint.  */
+
+void
+breakpt ()
+{
+  /* Just spin.  When the test is started under GDB we never enter the spin
+     loop, but when we attach, the worker thread will be spinning here.  */
+  while (dont_exit_just_yet)
+    sleep (1);
+}
+
+/* Thread function, doesn't do anything but hit a breakpoint.  */
+
+void *
+thread_worker (void *arg)
+{
+  breakpt ();
+  return NULL;
+}
+
+int
+main ()
+{
+  pthread_t thr;
+
+  /* Create a thread.  */
+  pthread_create (&thr, NULL, thread_worker, NULL);
+  pthread_detach (thr);
+
+  /* Spin until GDB releases us.  */
+  while (dont_exit_just_yet)
+    sleep (1);
+
+  _exit (0);
+}
diff --git a/gdb/testsuite/gdb.threads/main-thread-exit-during-detach.exp b/gdb/testsuite/gdb.threads/main-thread-exit-during-detach.exp
new file mode 100644
index 00000000000..97df3bd2783
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/main-thread-exit-during-detach.exp
@@ -0,0 +1,143 @@
+# Copyright 2023 Free Software Foundation, Inc.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Check for a race condition where in non-stop mode, the user might
+# have a thread other than the main (original) thread selected and use
+# the 'detach' command.
+#
+# As GDB tries to detach it is possible that the main thread might
+# exit, the main thread is still running due to non-stop mode.
+#
+# GDB used to assume that the main thread would always exist when
+# processing the detach, clearly this isn't the case, and this
+# assumption would lead to assertion failures and segfaults.
+#
+# Triggering the precise timing is pretty hard, we need the main
+# thread to exit after the user has entered the 'detach' command, but
+# before GDB enters the detach implementation and stops all threads,
+# the window of opportunity for this bug is actually tiny.
+#
+# However, we can trigger this bug 100% from Python, as GDB's
+# event-loop only kicks in once we return from a Python function.
+# Thus, if we have a single Python function that causes the main
+# thread to exit, and then calls detach GDB will not have a chance to
+# handle the main thread exiting before entering the detach code.
+
+standard_testfile
+
+require allow_python_tests
+
+if [build_executable "failed to prepare" $testfile $srcfile {debug pthreads}] {
+    return -1
+}
+
+# Run the test.  When SPAWN_INFERIOR is true the inferior is started
+# as a separate process which GDB then attaches too.  When
+# SPAWN_INFERIOR is false the inferior is started directly within GDB.
+
+proc run_test { spawn_inferior } {
+    global srcfile
+    global gdb_prompt
+    global GDBFLAGS
+
+    save_vars { GDBFLAGS } {
+	append GDBFLAGS " -ex \"set non-stop on\""
+	clean_restart $::binfile
+    }
+
+    # Setup the inferior.  When complete the main thread (#1) will
+    # still be running (due to non-stop mode), while the worker thread
+    # (#2) will be stopped.
+    #
+    # There are two setup modes, when SPAWN_INFERIOR is true we span a
+    # separate process and attach to it, after the attach both threads
+    # are stopped, so it is necessary to resume thread #1.
+    #
+    # When SPAWN_INFERIOR is false we just start the inferior within
+    # GDB, in this case we place a breakpoint that will be hit by
+    # thread #2.  When the breakpoint is hit thread #1 will remain
+    # running.
+    if {$spawn_inferior} {
+	set test_spawn_id [spawn_wait_for_attach $::binfile]
+	set testpid [spawn_id_get_pid $test_spawn_id]
+
+	set escapedbinfile  [string_to_regexp $::binfile]
+	gdb_test -no-prompt-anchor "attach $testpid" \
+	    "Attaching to program.*`?$escapedbinfile'?, process $testpid.*" \
+	    "attach to the inferior"
+
+	# Attaching to a multi-threaded application in non-stop mode
+	# can result in thread stops being reported after the prompt
+	# is displayed.
+	#
+	# Send a simple command now just to resync the command prompt.
+	gdb_test "p 1 + 2" " = 3"
+
+	# Set thread 1 (the current thread) running again.
+	gdb_test "continue&"
+    } else {
+	if {![runto_main]} {
+	    return -1
+	}
+
+	gdb_breakpoint "breakpt"
+	gdb_continue_to_breakpoint "run to breakpoint"
+    }
+
+    # Switch to thread 2.
+    gdb_test "thread 2" \
+	[multi_line \
+	     "Switching to thread 2\[^\r\n\]*" \
+	     "#0\\s+.*"]
+
+    # Create a Python function that sets a variable in the inferior and
+    # then detaches.  Setting the variable in the inferior will allow the
+    # main thread to exit, we even sleep for a short while in order to
+    # give the inferior a chance to exit.
+    #
+    # However, we don't want GDB to notice the exit before we call detach,
+    # which is why we perform both these actions from a Python function.
+    gdb_test_multiline "Create worker function" \
+	"python" "" \
+	"import time" "" \
+	"def set_and_detach():" "" \
+	"   gdb.execute(\"set variable dont_exit_just_yet=0\")" "" \
+	"   time.sleep(1)" "" \
+	"   gdb.execute(\"detach\")" "" \
+	"end" ""
+
+    # The Python function performs two actions, the first causes the
+    # main thread to exit, while the second detaches from the inferior.
+    #
+    # In both cases the stop arrives while GDB is processing the
+    # detach, however, for remote targets GDB doesn't report the stop,
+    # while for local targets GDB does report the stop.
+    if {![gdb_is_target_remote]} {
+	set stop_re "\\\[Thread.*exited\\\]\r\n"
+    } else {
+	set stop_re ""
+    }
+    gdb_test "python set_and_detach()" \
+	"${stop_re}\\\[Inferior.*detached\\\]"
+}
+
+foreach_with_prefix spawn_inferior { true false } {
+    if {$spawn_inferior && ![can_spawn_for_attach]} {
+	# If spawning (and attaching too) a separate inferior is not
+	# supported for the current board, then lets not try too.
+	continue
+    }
+
+    run_test $spawn_inferior
+}
diff --git a/gdbserver/mem-break.cc b/gdbserver/mem-break.cc
index 897b9a2273c..12143caf104 100644
--- a/gdbserver/mem-break.cc
+++ b/gdbserver/mem-break.cc
@@ -976,6 +976,13 @@ static struct gdb_breakpoint *
 find_gdb_breakpoint (char z_type, CORE_ADDR addr, int kind)
 {
   struct process_info *proc = current_process ();
+
+  /* GDB might not yet be aware that the current process has exited and so
+     still requests that we modify (delete) a breakpoint.  Just claim we
+     can't find the breakpoint.  */
+  if (proc == nullptr)
+    return nullptr;
+
   struct breakpoint *bp;
   enum bkpt_type type = Z_packet_to_bkpt_type (z_type);
 
