diff --git a/pypsi/os/unix.py b/pypsi/os/unix.py
index 65021f0..03b83a6 100644
--- a/pypsi/os/unix.py
+++ b/pypsi/os/unix.py
@@ -60,14 +60,18 @@ def make_ansi_stream(stream, **kwargs):
 
 def find_bins_in_path():
     bins = set()
-    paths = [x for x in os.environ['PATH'].split(':') if x.strip()]
+    paths = [x for x in os.environ.get('PATH', '').split(':') if x.strip()]
     paths.append('./')
     for path in paths:
         path = path or './'
-        for entry in os.listdir(path):
-            p = os.path.join(path, entry)
-            if os.path.isfile(p) and os.access(p, os.X_OK):
-                bins.add(entry)
+        try:
+            for entry in os.listdir(path):
+                p = os.path.join(path, entry)
+                if os.path.isfile(p) and os.access(p, os.X_OK):
+                    bins.add(entry)
+        except:
+            # bare except here because if this fails, tab completion can be entirely broken
+            pass
     return bins
 
 

Helper function `find_bins_in_path` can raise unhandled exception
When using the helper function `find_bins_in_path` on linux, if an item exists in your PATH variable that does not exist on disk, an exception will be raised and _all tab completion will be broken_, not just bin completion.


### Steps to Reproduce

```bash
# run demo.py with nonexistent item on PATH
PATH="$PATH:nonexistent" python3 demo.py
# ...
[19:50:51] pypsi )> mac<tab>  # observe pressing tab here does nothing
```
diff --git a/test/test_completers/test_find_bins_in_path.py b/test/test_completers/test_find_bins_in_path.py
new file mode 100644
index 0000000..3ece0da
--- /dev/null
+++ b/test/test_completers/test_find_bins_in_path.py
@@ -0,0 +1,48 @@
+from unittest.mock import patch
+
+from pypsi.os import find_bins_in_path
+
+
+# Example Data
+DIR1 = ['cat', 'grep', 'find', 'sort']
+DIR2 = ['awk', 'sed', 'ed', 'tr']
+
+
+class TestPathCompleter:
+
+    def setup(self):
+        self.listdir_patch = patch('os.listdir')
+        self.listdir_mock = self.listdir_patch.start()
+
+        self.isfile_patch = patch('os.path.isfile')
+        self.isfile_mock = self.isfile_patch.start()
+
+        self.access_patch = patch('os.access')
+        self.access_mock = self.access_patch.start()
+
+        # Always pretend files are real and executable
+        self.isfile_patch.return_value = True
+        self.access_patch.return_value = True
+
+    def teardown(self):
+        self.listdir_mock.stop()
+        self.isfile_mock.stop()
+        self.access_mock.stop()
+
+    def test_path_contains_nonexistent_path(self):
+        self.listdir_mock.side_effect = [DIR1, FileNotFoundError, DIR2]
+        ans = find_bins_in_path()
+
+        bins = set()
+        bins.update(DIR1)
+        bins.update(DIR2)
+        assert ans == bins
+
+    def test_permission_denied(self):
+        self.listdir_mock.side_effect = [DIR1, PermissionError, DIR2]
+        ans = find_bins_in_path()
+
+        bins = set()
+        bins.update(DIR1)
+        bins.update(DIR2)
+        assert ans == bins

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
3.6
requirements/requirements.txt requirements/requirements-dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
astroid==2.11.7
attrs==22.2.0
certifi==2021.5.30
chardet==5.0.0
coverage==6.2
dill==0.3.4
importlib-metadata==4.8.3
iniconfig==1.1.1
isort==5.10.1
lazy-object-proxy==1.7.1
mccabe==0.7.0
packaging==21.3
platformdirs==2.4.0
pluggy==1.0.0
py==1.11.0
pycodestyle==2.10.0
pylint==2.13.9
pyparsing==3.1.4
-e git+https://github.com/ameily/pypsi.git@38dda442b21b8deb569d61076ab0a19c0e78edc8#egg=pypsi
pytest==7.0.1
pytest-cov==4.0.0
tomli==1.2.3
typed-ast==1.5.5
typing_extensions==4.1.1
wrapt==1.16.0
zipp==3.6.0

name: pypsi
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - astroid==2.11.7
      - attrs==22.2.0
      - chardet==5.0.0
      - coverage==6.2
      - dill==0.3.4
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - isort==5.10.1
      - lazy-object-proxy==1.7.1
      - mccabe==0.7.0
      - packaging==21.3
      - platformdirs==2.4.0
      - pluggy==1.0.0
      - py==1.11.0
      - pycodestyle==2.10.0
      - pylint==2.13.9
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - tomli==1.2.3
      - typed-ast==1.5.5
      - typing-extensions==4.1.1
      - wrapt==1.16.0
      - zipp==3.6.0
prefix: /opt/conda/envs/pypsi

test/test_completers/test_find_bins_in_path.py::TestPathCompleter::test_path_contains_nonexistent_path test/test_completers/test_find_bins_in_path.py::TestPathCompleter::test_permission_denied
pypsi/os/unix.py
pypsi/os/unix.py