diff --git a/README.md b/README.md
index 59fa4850f006d4c0b6899206c337fd16c292cd4c..59d1802234bc191581bf95a4790d63f8491aa5e3 100644
--- a/README.md
+++ b/README.md
@@ -287,8 +287,8 @@ Instructions for testing
             itself, or to make it reload an edited `localconfig.vdf`?
 
 * Run the game. If successful, you will get a GUI launcher that lists possible
-    runtimes. Choose one, uncheck "Run in an xterm" for now, and click Run.
-    You should get a game. `pstree -pa` will look something like this:
+    runtimes. Choose one and click Run. You should get a game.
+    `pstree -pa` will look something like this:
 
         |-SteamChildMonit,13109
         |   `-sh,13110 -c...
@@ -376,10 +376,10 @@ configured.
             /opt/pressure-vessel/bin/pressure-vessel-wrap -- ./whatever-game
 
       Optionally add more options before the `--`, such as `--runtime`,
-      `--xterm` and `--interactive`.
+      `--xterm` and `--shell-instead`.
 
     In particular, if the `xterm` can't be launched and you are debugging
-    why, use the `--interactive` option to get a shell in the container.
+    why, use the `--tty` option to get a shell in the container.
 
     If `bwrap` is failing to start up and so you can't get a shell in the
     container at all, construct a simpler-but-simpler `bwrap` command-line
@@ -389,7 +389,7 @@ configured.
     system (if no runtime) contains an `xterm` binary, you can use
     something like:
 
-        /opt/pressure-vessel/bin/pressure-vessel-unruntime --xterm -- %command%
+        /opt/pressure-vessel/bin/pressure-vessel-unruntime --shell-instead -- %command%
 
     to run an xterm containing an interactive shell. The interactive
     shell's current working directory matches the game's, and you can
@@ -398,19 +398,27 @@ configured.
     When ready, run `"$@"` in the interactive shell (with the double
     quotes included!) to run the game.
 
-    Checking "Run in an xterm" in the GUI launcher is the
-    same as `--xterm` on the command-line.
+    Or, use `--shell-after` or `--shell-fail` to try running the game first,
+    and then run a shell afterwards, either unconditionally or only if
+    the command fails.
+
+    Similarly, you can use `--xterm` to make the game's output appear in
+    an xterm without having an interactive shell.
+
+    When you use any of these options, Steam will treat the xterm as part
+    of the game, so you cannot launch the game again from the Steam UI
+    until you have exited from the xterm.
 
 * For interactive testing, if you ran Steam from a shell (not normally
-    valid on SteamOS!), you can use:
+    valid on SteamOS!), you can use something like:
 
-        /opt/pressure-vessel/bin/pressure-vessel-unruntime --interactive -- %command%
+        /opt/pressure-vessel/bin/pressure-vessel-unruntime --tty --shell-instead -- %command%
 
     This is a simpler version of `--xterm`, which uses the terminal from
     which you ran Steam instead of an xterm inside the container.
 
     The interactive shell's current working directory matches the game's.
-    As with `--xterm`, run `"$@"` in the interactive shell to run the game.
+    As with the xterm, run `"$@"` in the interactive shell to run the game.
 
 ### More options
 
@@ -535,8 +543,8 @@ the same as for `flatpak run --filesystem=host` in Flatpak.
 
 ### Interactive debugging
 
-`--interactive` and `--xterm` work by wrapping an increasingly long
-"adverb" command around the command to be run.
+`--tty`, `--xterm` and the `--shell` options work by wrapping an
+increasingly long "adverb" command around the command to be run.
 
 ### Unsharing the home directory
 
diff --git a/pressure-vessel-test-ui b/pressure-vessel-test-ui
index fdf001f04b580eaa13ec12e3a13e22ef78357636..8c9a31b616d9df5e06b6b19e87c36fb7fa5201d4 100755
--- a/pressure-vessel-test-ui
+++ b/pressure-vessel-test-ui
@@ -121,11 +121,25 @@ class Gui:
         row += 1
 
         self.xterm_check = Gtk.CheckButton.new_with_label('Run in an xterm')
-        self.xterm_check.set_active(True)
+        self.xterm_check.set_active(False)
         self.grid.attach(self.xterm_check, 1, row, 1, 1)
 
         row += 1
 
+        shell_label = Gtk.Label.new('Run an interactive shell')
+        self.grid.attach(shell_label, 0, row, 1, 1)
+
+        self.shell_combo = Gtk.ComboBoxText.new()
+        self.shell_combo.append('', 'No')
+        self.shell_combo.append('--shell-after', 'After running the command')
+        self.shell_combo.append('--shell-fail', 'If the command fails')
+        self.shell_combo.append(
+            '--shell-instead', 'Instead of running the command')
+        self.shell_combo.set_active(0)
+        self.grid.attach(self.shell_combo, 1, row, 1, 1)
+
+        row += 1
+
         buttons_grid = Gtk.Grid(
             column_spacing=6,
             column_homogeneous=True,
@@ -261,6 +275,11 @@ class Gui:
         if self.xterm_check.get_active():
             argv.append('--xterm')
 
+        id = self.shell_combo.get_active_id()
+
+        if id is not None and id != '':
+            argv.append(id)
+
         argv.append('--verbose')
         argv.extend(sys.argv[1:])
 
diff --git a/src/wrap.c b/src/wrap.c
index a4e4bda5ab6859bb936aefba7346e019fc22afb4..7fe1554e48dd05f5c90e7d1e0a3a1ddcc4c5a398 100644
--- a/src/wrap.c
+++ b/src/wrap.c
@@ -1463,25 +1463,95 @@ wrap_in_xterm (FlatpakBwrap *wrapped_command)
 
   flatpak_bwrap_add_args (wrapped_command,
                           "xterm", "-e",
+                          /* Original command will go here and become
+                           * the argv of xterm -e */
+                          NULL);
+}
+
+typedef enum
+{
+  SHELL_NONE = 0,
+  SHELL_AFTER,
+  SHELL_FAIL,
+  SHELL_INSTEAD
+} Shell;
+
+static void
+wrap_interactive (FlatpakBwrap *wrapped_command,
+                  Shell shell)
+{
+  static const char preamble[] =
+    {
+      "prgname=\"$1\"\n"
+      "shift\n"
+    };
+  const char *script = "";
+  static const char start_shell[] =
+    {
+      "echo\n"
+      "echo\n"
+      "echo\n"
+      "echo \"$1: Starting interactive shell (original command is in "
+      "\\\"\\$@\\\")\"\n"
+      "echo\n"
+      "echo\n"
+      "echo\n"
+      "exec bash -i -s \"$@\"\n"
+    };
+  gchar *command;
+
+  g_return_if_fail (wrapped_command != NULL);
+
+  switch (shell)
+    {
+      case SHELL_NONE:
+        script =
+          "e=0\n"
+          "\"$@\" || e=$?\n"
+          "echo\n"
+          "echo \"Press Enter or ^D to continue...\"\n"
+          "read reply || true\n"
+          "exit \"$e\"\n";
+        break;
+
+      case SHELL_AFTER:
+        script =
+          "e=0\n"
+          "\"$@\" || e=$?\n";
+        break;
+
+      case SHELL_FAIL:
+        script =
+          "if \"$@\"; then exit 0; else e=\"$?\"; fi\n"
+          "echo \"$1: command exit status $e\"\n";
+        break;
+
+      case SHELL_INSTEAD:
+        break;
+
+      default:
+        g_return_if_reached ();
+    }
+
+  command = g_strdup_printf ("%s%s%s",
+                             preamble,
+                             script,
+                             shell == SHELL_NONE ? "" : start_shell);
+
+  flatpak_bwrap_add_args (wrapped_command,
                           "sh", "-euc",
-                          "echo\n"
-                          "echo \"$1: Starting interactive shell "
-                                 "(original command is in "
-                                 "\\\"\\$@\\\")\"\n"
-                          "echo\n"
-                          "shift\n"
-                          "exec \"$@\"\n",
+                          command,
                           "sh",   /* $0 for sh */
                           g_get_prgname (),   /* $1 for sh */
-                          "bash", "-i", "-s",
                           /* Original command will go here and become
+                           * the argv of command, and eventually
                            * the argv of bash -i -s */
                           NULL);
 }
 
 static gboolean
-wrap_interactive (FlatpakBwrap *wrapped_command,
-                  GError **error)
+wrap_tty (FlatpakBwrap *wrapped_command,
+          GError **error)
 {
   int fd;
 
@@ -1514,15 +1584,6 @@ wrap_interactive (FlatpakBwrap *wrapped_command,
     return glnx_throw_errno_prefix (error, "Cannot use /dev/tty as stderr");
 
   g_close (fd, NULL);
-
-  flatpak_bwrap_add_args (wrapped_command,
-                          "bash", "-i", "-s",
-                          /* Original command will go here and become
-                           * the argv of bash -i -s */
-                          NULL);
-
-  g_message ("Starting interactive shell "
-             "(original command is in \"$@\"");
   return TRUE;
 }
 
@@ -1532,12 +1593,15 @@ static char *opt_freedesktop_app_id = NULL;
 static char *opt_steam_app_id = NULL;
 static char *opt_home = NULL;
 static gboolean opt_host_fallback = FALSE;
-static gboolean opt_interactive = FALSE;
+static gboolean opt_shell_after = FALSE;
+static gboolean opt_shell_fail = FALSE;
+static gboolean opt_shell_instead = FALSE;
 static GPtrArray *opt_ld_preload = NULL;
 static char *opt_runtime = NULL;
 static gboolean opt_share_home = TRUE;
 static gboolean opt_verbose = FALSE;
 static gboolean opt_version = FALSE;
+static gboolean opt_tty = FALSE;
 static gboolean opt_xterm = FALSE;
 
 static gboolean
@@ -1575,11 +1639,6 @@ static GOptionEntry options[] =
   { "host-ld-preload", 0, 0, G_OPTION_ARG_CALLBACK, &opt_host_ld_preload_cb,
     "Add MODULE from the host system to LD_PRELOAD when executing COMMAND.",
     "MODULE" },
-  { "interactive", 0, 0, G_OPTION_ARG_NONE, &opt_interactive,
-    "Run an interactive shell instead of COMMAND. Executing \"$@\" in that "
-    "shell will run COMMAND [ARGS]. This process must have a controlling "
-    "terminal.",
-    NULL },
   { "runtime", 0, 0, G_OPTION_ARG_FILENAME, &opt_runtime,
     "Mount the given sysroot or merged /usr in the container, and augment "
     "it with the host system's graphics stack.",
@@ -1589,9 +1648,26 @@ static GOptionEntry options[] =
   { "unshare-home", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_share_home,
     "Use an app-specific home directory chosen according to --home, "
     "--freedesktop-app-id, --steam-app-id or $SteamAppId.", NULL },
+  { "shell-after", 0, 0, G_OPTION_ARG_NONE, &opt_shell_after,
+    "Run an interactive shell after COMMAND. Executing \"$@\" in that "
+    "shell will re-run COMMAND [ARGS].",
+    NULL },
+  { "shell-fail", 0, 0, G_OPTION_ARG_NONE, &opt_shell_fail,
+    "Run an interactive shell after COMMAND, but only if it fails.",
+    NULL },
+  { "shell-instead", 0, 0, G_OPTION_ARG_NONE, &opt_shell_instead,
+    "Run an interactive shell instead of COMMAND. Executing \"$@\" in that "
+    "shell will run COMMAND [ARGS].",
+    NULL },
+  { "tty", 0, 0, G_OPTION_ARG_NONE, &opt_tty,
+    "For --shell-after, --shell-fail and --shell-instead, use Steam's "
+    "controlling tty instead of running an xterm.",
+    NULL },
   { "xterm", 0, 0, G_OPTION_ARG_NONE, &opt_xterm,
-    "Same as --interactive, but run the shell in an xterm. xterm(1) "
-    "must be installed in the RUNTIME, if used.", NULL },
+    "Run an xterm which will display the log output from the game. "
+    "Implied by --shell-after, --shell-fail and --shell-instead, "
+    "unless --tty is used.",
+    NULL },
   { "verbose", 0, 0, G_OPTION_ARG_NONE, &opt_verbose,
     "Be more verbose.", NULL },
   { "version", 0, 0, G_OPTION_ARG_NONE, &opt_version,
@@ -1680,6 +1756,18 @@ main (int argc,
       goto out;
     }
 
+  if (opt_tty && opt_xterm)
+    {
+      g_printerr ("%s: --tty and --xterm are contradictory\n",
+                  g_get_prgname ());
+      goto out;
+    }
+
+  if ((opt_shell_after || opt_shell_fail || opt_shell_instead) && !opt_tty)
+    {
+      opt_xterm = TRUE;
+    }
+
   if (strcmp (argv[1], "--") == 0)
     {
       argv++;
@@ -1740,7 +1828,7 @@ main (int argc,
    * us exit 1. */
   ret = 1;
 
-  if (!opt_interactive)
+  if (!opt_tty)
     {
       int fd;
 
@@ -1808,16 +1896,37 @@ main (int argc,
 
   wrapped_command = flatpak_bwrap_new (NULL);
 
+  if (opt_tty)
+    {
+      g_debug ("Wrapping command to use tty");
+
+      if (!wrap_tty (wrapped_command, error))
+        goto out;
+    }
+
   if (opt_xterm)
     {
       g_debug ("Wrapping command with xterm");
       wrap_in_xterm (wrapped_command);
     }
-  else if (opt_interactive)
+
+  if (opt_shell_instead)
     {
-      g_debug ("Wrapping command with interactive shell");
-      if (!wrap_interactive (wrapped_command, error))
-        goto out;
+      wrap_interactive (wrapped_command, SHELL_INSTEAD);
+    }
+  else if (opt_shell_after)
+    {
+      wrap_interactive (wrapped_command, SHELL_AFTER);
+    }
+  else if (opt_shell_fail)
+    {
+      wrap_interactive (wrapped_command, SHELL_FAIL);
+    }
+  else if (opt_xterm)
+    {
+      /* Just don't let the xterm close before the user has had a chance
+       * to see the output */
+      wrap_interactive (wrapped_command, SHELL_NONE);
     }
 
   if (argv[1][0] == '-')
@@ -1903,7 +2012,7 @@ main (int argc,
   /* Protect the controlling terminal from the app/game, unless we are
    * running an interactive shell in which case that would break its
    * job control. */
-  if (!opt_interactive)
+  if (!opt_tty)
     flatpak_bwrap_add_arg (bwrap, "--new-session");
 
   if (opt_runtime != NULL)