Repository: xonsh/xonsh

Issue:
[regression] `rm -i` prompt is failing (again)
### xonfig

<details>

```
$ xonfig
+------------------+-----------------+
| xonsh            | 0.8.10          |
| Python           | 3.7.2           |
| PLY              | 3.11            |
| have readline    | True            |
| prompt toolkit   | 2.0.8           |
| shell type       | prompt_toolkit2 |
| pygments         | 2.3.1           |
| on posix         | True            |
| on linux         | True            |
| distro           | arch            |
| on darwin        | False           |
| on windows       | False           |
| on cygwin        | False           |
| on msys2         | False           |
| is superuser     | False           |
| default encoding | utf-8           |
| xonsh encoding   | utf-8           |
| encoding errors  | surrogateescape |
+------------------+-----------------+
```

</details>

## issue

Looked through the old issues and it seems like this problem should be solved. So this might be a regression.

```
$ xonsh --no-rc
$ touch file1 file2 file3 file4
$ rm -i file*
/usr/bin/rm: remove regular empty file 'file1'? /usr/bin/rm: remove regular empty file 'file2'? /usr/bin/rm: remove regular empty file 'file3'? /usr/bin/rm: remove regular empty file 'file4'? /usr/bin/rm: error closing file
```

I checked that `rm` is not aliased to anything else in Bash. I also tried with `xonsh --no-rc --shell-type readline` with the same result. Finally, setting `$XONSH_STORE_STDOUT` to True or False makes no difference.

### versions

<details>

```
$ rm --version
rm (GNU coreutils) 8.30
$ uname -a
Linux 4.20.8-arch1-1-ARCH #1 SMP PREEMPT Wed Feb 13 00:06:58 UTC 2019 x86_64 GNU/Linux
```

</details>


Discussion:
Thanks for raising this, @mogenson!  I can confirm the regression and I'm pretty sure it's related to #3015 

Temporary workaround is to put the following in your `xonshrc`

```
__xonsh__.commands_cache.threadable_predictors['rm'] = lambda x: False
```
Actually, doing a git bisect between 0.8.9 and 0.8.10 shows that it is e0aa871df71f24d951f726ed95eba6e91803d7ca which is the problem.  This is actually #3004.

Resolving patch:
diff --git a/xonsh/jobs.py b/xonsh/jobs.py
--- a/xonsh/jobs.py
+++ b/xonsh/jobs.py
@@ -184,7 +184,7 @@ def give_terminal_to(pgid):
                 # only reset the mask if it is non-empty! See #2989
                 _pthread_sigmask(signal.SIG_SETMASK, oldmask)
 
-    def wait_for_active_job(last_task=None, backgrounded=False, _nochild=False):
+    def wait_for_active_job(last_task=None, backgrounded=False, return_error=False):
         """
         Wait for the active job to finish, to be killed by SIGINT, or to be
         suspended by ctrl-z.
@@ -198,12 +198,12 @@ def wait_for_active_job(last_task=None, backgrounded=False, _nochild=False):
         backgrounded = False
         try:
             _, wcode = os.waitpid(obj.pid, os.WUNTRACED)
-        except ChildProcessError:  # No child processes
-            if _nochild:
-                return active_task
+        except ChildProcessError as e:  # No child processes
+            if return_error:
+                return e
             else:
-                return wait_for_active_job(
-                    last_task=active_task, backgrounded=backgrounded, _nochild=True
+                return _safe_wait_for_active_job(
+                    last_task=active_task, backgrounded=backgrounded
                 )
         if os.WIFSTOPPED(wcode):
             print("^Z")
@@ -219,6 +219,20 @@ def wait_for_active_job(last_task=None, backgrounded=False, _nochild=False):
         return wait_for_active_job(last_task=active_task, backgrounded=backgrounded)
 
 
+def _safe_wait_for_active_job(last_task=None, backgrounded=False):
+    """Safely call wait_for_active_job()"""
+    have_error = True
+    while have_error:
+        try:
+            rtn = wait_for_active_job(
+                last_task=last_task, backgrounded=backgrounded, return_error=True
+            )
+        except ChildProcessError as e:
+            rtn = e
+        have_error = isinstance(rtn, ChildProcessError)
+    return rtn
+
+
 def get_next_task():
     """ Get the next active task and put it on top of the queue"""
     selected_task = None


Test patch: