Repository: xonsh/xonsh

Issue:
XONSH_HISTORY_FILE is set to PosixPath('None') on Fedora+GNOME
<!--- Provide a general summary of the issue in the Title above -->
<!--- If you have a question along the lines of "How do I do this Bash command in xonsh"
please first look over the Bash to Xonsh translation guide: https://xon.sh/bash_to_xsh.html
If you don't find an answer there, please do open an issue! -->

## xonfig

<details>

```
$ xonfig
+------------------+---------------------------------+
| xonsh            | 0.11.0                          |
| Git SHA          | 337cf25a                        |
| Commit Date      | Nov 17 15:37:41 2021            |
| Python           | 3.10.0                          |
| PLY              | 3.11                            |
| have readline    | True                            |
| prompt toolkit   | 3.0.5                           |
| shell type       | readline                        |
| history backend  | json                            |
| pygments         | 2.9.0                           |
| on posix         | True                            |
| on linux         | True                            |
| distro           | fedora                          |
| on wsl           | False                           |
| 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                 |
| on jupyter       | False                           |
| jupyter kernel   | None                            |
| xontrib 1        | vox                             |
| xontrib 2        | voxapi                          |
| RC file 1        | /var/home/carmenbianca/.xonshrc |
+------------------+---------------------------------+
```

</details>

## Expected Behavior
<!--- Tell us what should happen -->

When using Xonsh as under GNOME, it should behave exactly as though it was launched from a TTY.

## Current Behavior
<!--- Tell us what happens instead of the expected behavior -->
<!--- If part of your bug report is a traceback, please first enter debug mode before triggering the error
To enter debug mode, set the environment variable `XONSH_DEBUG=1` _before_ starting `xonsh`.
On Linux and OSX, an easy way to to do this is to run `env XONSH_DEBUG=1 xonsh` -->

`$XONSH_HISTORY_FILE` is set to `PosixPath('None')` when xonsh is launched in GNOME. This is rather annoying, because it creates a `None` file in whichever directory you happen to launch xonsh from.

The above applies to _all_ shells launched as subprocesses of the login shell process, which is basically every process. It also applies to non-system xonsh processes (e.g., xonsh launched in a virtualenv).

This does not apply to xonsh launched in a raw TTY.

I'm rather out of ideas on what could be causing this.

## Steps to Reproduce
<!--- Please try to write out a minimal reproducible snippet to trigger the bug, it will help us fix it! -->
1. Install Fedora Workstation (or Silverblue)
2. `sudo dnf install xonsh`
3. `xonsh`
4. `$XONSH_HISTORY_FILE`

(ignore the PTK warnings; it's outdated and desperately needs updating, but otherwise unrelated.)

The presence of a `.xonshrc` file changes nothing.

For the purpose of this issue, `/bin/bash` is the default login shell of the system.

## Furthermore

The xonsh.spec can be found [here](https://src.fedoraproject.org/rpms/xonsh/blob/rawhide/f/xonsh.spec).

The build output can be found [here](https://koji.fedoraproject.org/koji/buildinfo?buildID=1867653).

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**

XONSH_HISTORY_FILE is set to PosixPath('None') on Fedora+GNOME
<!--- Provide a general summary of the issue in the Title above -->
<!--- If you have a question along the lines of "How do I do this Bash command in xonsh"
please first look over the Bash to Xonsh translation guide: https://xon.sh/bash_to_xsh.html
If you don't find an answer there, please do open an issue! -->

## xonfig

<details>

```
$ xonfig
+------------------+---------------------------------+
| xonsh            | 0.11.0                          |
| Git SHA          | 337cf25a                        |
| Commit Date      | Nov 17 15:37:41 2021            |
| Python           | 3.10.0                          |
| PLY              | 3.11                            |
| have readline    | True                            |
| prompt toolkit   | 3.0.5                           |
| shell type       | readline                        |
| history backend  | json                            |
| pygments         | 2.9.0                           |
| on posix         | True                            |
| on linux         | True                            |
| distro           | fedora                          |
| on wsl           | False                           |
| 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                 |
| on jupyter       | False                           |
| jupyter kernel   | None                            |
| xontrib 1        | vox                             |
| xontrib 2        | voxapi                          |
| RC file 1        | /var/home/carmenbianca/.xonshrc |
+------------------+---------------------------------+
```

</details>

## Expected Behavior
<!--- Tell us what should happen -->

When using Xonsh as under GNOME, it should behave exactly as though it was launched from a TTY.

## Current Behavior
<!--- Tell us what happens instead of the expected behavior -->
<!--- If part of your bug report is a traceback, please first enter debug mode before triggering the error
To enter debug mode, set the environment variable `XONSH_DEBUG=1` _before_ starting `xonsh`.
On Linux and OSX, an easy way to to do this is to run `env XONSH_DEBUG=1 xonsh` -->

`$XONSH_HISTORY_FILE` is set to `PosixPath('None')` when xonsh is launched in GNOME. This is rather annoying, because it creates a `None` file in whichever directory you happen to launch xonsh from.

The above applies to _all_ shells launched as subprocesses of the login shell process, which is basically every process. It also applies to non-system xonsh processes (e.g., xonsh launched in a virtualenv).

This does not apply to xonsh launched in a raw TTY.

I'm rather out of ideas on what could be causing this.

## Steps to Reproduce
<!--- Please try to write out a minimal reproducible snippet to trigger the bug, it will help us fix it! -->
1. Install Fedora Workstation (or Silverblue)
2. `sudo dnf install xonsh`
3. `xonsh`
4. `$XONSH_HISTORY_FILE`

(ignore the PTK warnings; it's outdated and desperately needs updating, but otherwise unrelated.)

The presence of a `.xonshrc` file changes nothing.

For the purpose of this issue, `/bin/bash` is the default login shell of the system.

## Furthermore

The xonsh.spec can be found [here](https://src.fedoraproject.org/rpms/xonsh/blob/rawhide/f/xonsh.spec).

The build output can be found [here](https://koji.fedoraproject.org/koji/buildinfo?buildID=1867653).

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**



Discussion:
I can repeat this in docker:
```python
docker run --rm -it fedora /bin/bash \
      -c @("yum install -y pip && pip install xonsh && echo 'echo $XONSH_HISTORY_FILE' > ~/.xonshrc && xonsh")
# None
history info
# filename: /root/.local/share/xonsh/history_json/xonsh-fe6369ad-ea73-4e4d-a89f-7fbece31555b.json

```
@anki-code Docker is unlikely to replicate the exact behaviour. I think what's happening in the above example is that $XONSH_HISTORY_FILE is set _after_ sourcing `.xonshrc`.

On a live system with GNOME, you'd get something like this:

```
$ echo 'echo $XONSH_HISTORY_FILE' > ~/.xonshrc
$ xonsh
None
$ history info
filename: None
```
I've got this in docker:
```python
docker run --rm -it fedora /bin/bash \
        -c @("yum install -y pip && pip install xonsh && xonsh")

$XONSH_HISTORY_FILE
# PosixPath('/root/.local/share/xonsh/history_json/xonsh-5c230063-5331-4b5f-8b78-8ecfd5139a33.json')
history info
# filename: /root/.local/share/xonsh/history_json/xonsh-5c230063-5331-4b5f-8b78-8ecfd5139a33.json
```

And probably the start point to diving into this issue here:

https://github.com/xonsh/xonsh/blob/c2d25ac2519fa4edb21f2c8e155495963ad8a27d/xonsh/shell.py#L204-L214
~~On my end, `$XONSH_INTERACTIVE` is not set inside of GNOME. It _is_ set in a tty.~~ edit: That was wrong. I did a typo.

Weird. I might look into this next week.
Hey @carmenbianca !  This is a weird one.  Can you clarify how you're opening the terminal when it isn't working?  Is this via the "open terminal here" option in Nautilus or something else?  
@gforsyth Like normal. Open the Activities view, search for 'Terminal' (gnome-terminal), and open it.

The same happens in Guake.

I didn't do diligent maintenance on Xonsh for a while, so I believe this bug was introduced either in Fedora 34 or Xonsh 0.9.X/0.10.X.
Hi,

I have the same issue with xonsh 0.11.0 (337cf25a) on Debian 11 (GNOME). I fixed it by replacing `if filename is None:` by `if str(filename) == 'None':` at line 411 of `history/json.py` (class JsonHistory). It looks like the `XONSH_HISTORY_FILE` variable is defined as a `pathlib.PosixPath` object in `environ.py`, so the original test can never be true.

I don't know Xonsh's code, so this fix is maybe not the right one, but it seems to fix the issue :-)
Forgot to mention that I also patched the amalgamate file at line 657.
Sorry, forget about my previous comments. My problem is that actually I have `XONSH_HISTORY_FILE=None` and other XONSH variables set in my environment, which I guess, overrides the default settings in `environ.py`. There are only set when I launch a terminal from GNOME, not from a TTY, as already reported. ~~Even when I switch back to Bash, the variables are there.~~ The variables went away after restarting GDM. So I need to figure out where those variables are set.
Just an update about what I found. The problem does not exist when I start a X11 session, only on Wayland. Looking at the processus tree, I noticed that the GNOME session is started with Xonsh:
```
├─gdm3
  │   ├─gdm-session-worker
  │   │   ├─gdm-wayland-session /usr/bin/gnome-session
  │   │   │   ├─xonsh -c /usr/bin/gnome-session -l
  │   │   │   │   └─gnome-session-binary --systemd

```
So I presume this is this invocation which sets the variables in the environment. To confirm that, I replaced the `SHELL` variable by `/bin/bash` at line 9 of `/usr/bin/gnome-session` and, indeed, this fixed the issue.

I am not sure. Maybe the right fix is to add a test for `None` in the `path_to_str` function:
```
def path_to_str(x):
    """Converts a path to a string."""
    if x is None:
        return None
    else:
        return str(x)
```
But I don't know what would be the impact on the whole code. I will give it a try.
Hello,

I have the same exact problem. However, my setup is a bit different:
<details>
<summary>Details</summary>

```
ale@AlePc ~ $ xonfig
+------------------+--------------------------------+
| xonsh            | 0.11.0                         |
| Python           | 3.10.1                         |
| PLY              | 3.11                           |
| have readline    | True                           |
| prompt toolkit   | 3.0.24                         |
| shell type       | prompt_toolkit                 |
| history backend  | json                           |
| pygments         | 2.11.2                         |
| on posix         | True                           |
| on linux         | True                           |
| distro           | arch                           |
| on wsl           | False                          |
| 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                |
| on jupyter       | True                           |
| jupyter kernel   | None                           |
| xontrib 1        | vox                            |
| xontrib 2        | voxapi                         |
| RC file 1        | /home/ale/.config/xonsh/rc.xsh |
+------------------+--------------------------------+
```

</details>

I have been struggling to find the root of the problem; however:

- If I set `xonsh` as default shell using `chsh`, then:
  - opening a terminal from Plasma correctly sets $XONSH_HISTORY_FILE;
  - opening a terminal from bspwm sets $XONSH_HISTORY_FILE as `None`;
- if I set `zsh` as default shell using `chsh` then opening manually `xonsh` from a terminal sets correctly $XONSH_HISTORY_FILE, no matter the DE.

I am sorry I am not able to provide something more useful.

I think I have figured it out:

When an environment variable is set to None, and is inherited to a child process, it will be represented by (i.e. "detyped" to) the string "None".

Example:
```bash
# in xonsh:
$TEST = None
# create a subprocess
bash
echo $TEST
# prints "None"
```

In this specific case, the following occurs:
A xonsh process with `$XONSH_INTERACTIVE == False` sets its `$XONSH_HISTORY_FILE` to `None`. A child process of it with `$XONSH_INTERACTIVE == True` will now inherit it as "None" rather than None, and use it as the filename.

I propose that environment variables set to `None` shall not be inherited to subprocesses, as this seems like the most sane solution to me.

What do you suggest?


Nice job tracking that down @dev2718!
I don't think we want to dictate which env-vars should and shouldn't be inherited (also, that may not be entirely possible?).

I think there are two issues to tackle here:  
1. detyping `None` to `"None"` seems to have limited utility to me.  I would think the closest bashwards equivalent would be an empty string.  So that's one thing to fix.
2. If `$XONSH_HISTORY_FILE` is an empty string or `"None"` (pending the first thing) we should override and create a new history file for it.  To disable history, there is still `history off` or explicitly setting `$XONSH_HISTORY_FILE=None`
I can repeat this in docker:
```python
docker run --rm -it fedora /bin/bash \
      -c @("yum install -y pip && pip install xonsh && echo 'echo $XONSH_HISTORY_FILE' > ~/.xonshrc && xonsh")
# None
history info
# filename: /root/.local/share/xonsh/history_json/xonsh-fe6369ad-ea73-4e4d-a89f-7fbece31555b.json

```
@anki-code Docker is unlikely to replicate the exact behaviour. I think what's happening in the above example is that $XONSH_HISTORY_FILE is set _after_ sourcing `.xonshrc`.

On a live system with GNOME, you'd get something like this:

```
$ echo 'echo $XONSH_HISTORY_FILE' > ~/.xonshrc
$ xonsh
None
$ history info
filename: None
```
I've got this in docker:
```python
docker run --rm -it fedora /bin/bash \
        -c @("yum install -y pip && pip install xonsh && xonsh")

$XONSH_HISTORY_FILE
# PosixPath('/root/.local/share/xonsh/history_json/xonsh-5c230063-5331-4b5f-8b78-8ecfd5139a33.json')
history info
# filename: /root/.local/share/xonsh/history_json/xonsh-5c230063-5331-4b5f-8b78-8ecfd5139a33.json
```

And probably the start point to diving into this issue here:

https://github.com/xonsh/xonsh/blob/c2d25ac2519fa4edb21f2c8e155495963ad8a27d/xonsh/shell.py#L204-L214
~~On my end, `$XONSH_INTERACTIVE` is not set inside of GNOME. It _is_ set in a tty.~~ edit: That was wrong. I did a typo.

Weird. I might look into this next week.
Hey @carmenbianca !  This is a weird one.  Can you clarify how you're opening the terminal when it isn't working?  Is this via the "open terminal here" option in Nautilus or something else?  
@gforsyth Like normal. Open the Activities view, search for 'Terminal' (gnome-terminal), and open it.

The same happens in Guake.

I didn't do diligent maintenance on Xonsh for a while, so I believe this bug was introduced either in Fedora 34 or Xonsh 0.9.X/0.10.X.
Hi,

I have the same issue with xonsh 0.11.0 (337cf25a) on Debian 11 (GNOME). I fixed it by replacing `if filename is None:` by `if str(filename) == 'None':` at line 411 of `history/json.py` (class JsonHistory). It looks like the `XONSH_HISTORY_FILE` variable is defined as a `pathlib.PosixPath` object in `environ.py`, so the original test can never be true.

I don't know Xonsh's code, so this fix is maybe not the right one, but it seems to fix the issue :-)
Forgot to mention that I also patched the amalgamate file at line 657.
Sorry, forget about my previous comments. My problem is that actually I have `XONSH_HISTORY_FILE=None` and other XONSH variables set in my environment, which I guess, overrides the default settings in `environ.py`. There are only set when I launch a terminal from GNOME, not from a TTY, as already reported. ~~Even when I switch back to Bash, the variables are there.~~ The variables went away after restarting GDM. So I need to figure out where those variables are set.
Just an update about what I found. The problem does not exist when I start a X11 session, only on Wayland. Looking at the processus tree, I noticed that the GNOME session is started with Xonsh:
```
├─gdm3
  │   ├─gdm-session-worker
  │   │   ├─gdm-wayland-session /usr/bin/gnome-session
  │   │   │   ├─xonsh -c /usr/bin/gnome-session -l
  │   │   │   │   └─gnome-session-binary --systemd

```
So I presume this is this invocation which sets the variables in the environment. To confirm that, I replaced the `SHELL` variable by `/bin/bash` at line 9 of `/usr/bin/gnome-session` and, indeed, this fixed the issue.

I am not sure. Maybe the right fix is to add a test for `None` in the `path_to_str` function:
```
def path_to_str(x):
    """Converts a path to a string."""
    if x is None:
        return None
    else:
        return str(x)
```
But I don't know what would be the impact on the whole code. I will give it a try.
Hello,

I have the same exact problem. However, my setup is a bit different:
<details>
<summary>Details</summary>

```
ale@AlePc ~ $ xonfig
+------------------+--------------------------------+
| xonsh            | 0.11.0                         |
| Python           | 3.10.1                         |
| PLY              | 3.11                           |
| have readline    | True                           |
| prompt toolkit   | 3.0.24                         |
| shell type       | prompt_toolkit                 |
| history backend  | json                           |
| pygments         | 2.11.2                         |
| on posix         | True                           |
| on linux         | True                           |
| distro           | arch                           |
| on wsl           | False                          |
| 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                |
| on jupyter       | True                           |
| jupyter kernel   | None                           |
| xontrib 1        | vox                            |
| xontrib 2        | voxapi                         |
| RC file 1        | /home/ale/.config/xonsh/rc.xsh |
+------------------+--------------------------------+
```

</details>

I have been struggling to find the root of the problem; however:

- If I set `xonsh` as default shell using `chsh`, then:
  - opening a terminal from Plasma correctly sets $XONSH_HISTORY_FILE;
  - opening a terminal from bspwm sets $XONSH_HISTORY_FILE as `None`;
- if I set `zsh` as default shell using `chsh` then opening manually `xonsh` from a terminal sets correctly $XONSH_HISTORY_FILE, no matter the DE.

I am sorry I am not able to provide something more useful.

I think I have figured it out:

When an environment variable is set to None, and is inherited to a child process, it will be represented by (i.e. "detyped" to) the string "None".

Example:
```bash
# in xonsh:
$TEST = None
# create a subprocess
bash
echo $TEST
# prints "None"
```

In this specific case, the following occurs:
A xonsh process with `$XONSH_INTERACTIVE == False` sets its `$XONSH_HISTORY_FILE` to `None`. A child process of it with `$XONSH_INTERACTIVE == True` will now inherit it as "None" rather than None, and use it as the filename.

I propose that environment variables set to `None` shall not be inherited to subprocesses, as this seems like the most sane solution to me.

What do you suggest?


Nice job tracking that down @dev2718!
I don't think we want to dictate which env-vars should and shouldn't be inherited (also, that may not be entirely possible?).

I think there are two issues to tackle here:  
1. detyping `None` to `"None"` seems to have limited utility to me.  I would think the closest bashwards equivalent would be an empty string.  So that's one thing to fix.
2. If `$XONSH_HISTORY_FILE` is an empty string or `"None"` (pending the first thing) we should override and create a new history file for it.  To disable history, there is still `history off` or explicitly setting `$XONSH_HISTORY_FILE=None`

Resolving patch:
diff --git a/xonsh/tools.py b/xonsh/tools.py
--- a/xonsh/tools.py
+++ b/xonsh/tools.py
@@ -1226,8 +1226,8 @@ def always_none(x):
 
 
 def ensure_string(x):
-    """Returns a string if x is not a string, and x if it already is."""
-    return str(x)
+    """Returns a string if x is not a string, and x if it already is. If x is None, the empty string is returned."""
+    return str(x) if x is not None else ""
 
 
 def is_path(x):
@@ -1242,11 +1242,10 @@ def is_env_path(x):
 
 def str_to_path(x):
     """Converts a string to a path."""
-    if x is None:
+    if x is None or x == "":
         return None
     elif isinstance(x, str):
-        # checking x is needed to avoid uncontrolled converting empty string to Path('.')
-        return pathlib.Path(x) if x else None
+        return pathlib.Path(x)
     elif isinstance(x, pathlib.Path):
         return x
     elif isinstance(x, EnvPath) and len(x) == 1:
@@ -1267,7 +1266,7 @@ def str_to_env_path(x):
 
 def path_to_str(x):
     """Converts a path to a string."""
-    return str(x)
+    return str(x) if x is not None else ""
 
 
 def env_path_to_str(x):


Test patch:
diff --git a/tests/test_environ.py b/tests/test_environ.py
--- a/tests/test_environ.py
+++ b/tests/test_environ.py
@@ -443,7 +443,7 @@ def test_register_custom_var_bool(val, converted):
         (32, "32"),
         (0, "0"),
         (27.0, "27.0"),
-        (None, "None"),
+        (None, ""),
         ("lol", "lol"),
         ("false", "false"),
         ("no", "no"),
diff --git a/tests/test_tools.py b/tests/test_tools.py
--- a/tests/test_tools.py
+++ b/tests/test_tools.py
@@ -1627,8 +1627,8 @@ def test_expand_case_matching(inp, exp):
         ("$unk $foo $unk $spam $unk", "$unk bar $unk eggs $unk"),
         ("$an_int$spam$a_bool", "42eggsTrue"),
         ("$unk$an_int$spam$a_bool", "$unk42eggsTrue"),
-        ("bar$foo$spam$foo $an_int $none", "barbareggsbar 42 None"),
-        ("$unk bar$foo$spam$foo $an_int $none", "$unk barbareggsbar 42 None"),
+        ("bar$foo$spam$foo $an_int $none", "barbareggsbar 42 "),
+        ("$unk bar$foo$spam$foo $an_int $none", "$unk barbareggsbar 42 "),
         ("$foo/bar", "bar/bar"),
         ("$unk/$foo/bar", "$unk/bar/bar"),
         ("${'foo'} $spam", "bar eggs"),
@@ -1688,7 +1688,7 @@ def test_ensure_timestamp(inp, fmt, exp, xession):
         ("~/$foo", True, "/bar"),
         ("~/test/$a_bool", True, "/test/True"),
         ("~/test/$an_int", True, "/test/42"),
-        ("~/test/$none", True, "/test/None"),
+        ("~/test/$none", True, "/test/"),
         ("~/$foo", False, "/$foo"),
     ],
 )