diff --git a/CHANGES.md b/CHANGES.md
index cdc413c64..625213baa 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -11,6 +11,15 @@ ones in. -->
--------------------------------------------------------------------------------
+## 2.2.0 (Upcoming)
+
+### Breaking Changes
+
+[#2736](https://github.com/metomi/rose/pull/2736) -
+Rose now ignores `PYTHONPATH` to make it more robust to task environments
+which set this value. If you want to add to the Rose environment itself,
+e.g. to write a rose-ana test, use `ROSE_PYTHONPATH`.
+
## 2.1.0 (Released 2023-07-21)
### Fixes
diff --git a/metomi/rose/rose.py b/metomi/rose/rose.py
index be4606cff..9b0f06532 100644
--- a/metomi/rose/rose.py
+++ b/metomi/rose/rose.py
@@ -14,11 +14,42 @@
#
# You should have received a copy of the GNU General Public License
# along with Rose. If not, see .
+
+import os
+import sys
+
+
+def pythonpath_manip():
+ """Stop PYTHONPATH contaminating the Rose Environment
+
+ * Remove PYTHONPATH items from sys.path to prevent PYTHONPATH
+ contaminating the Rose Environment.
+ * Add items from ROSE_PYTHONPATH to sys.path.
+
+ See Also:
+ https://github.com/cylc/cylc-flow/issues/5124
+ """
+ if 'ROSE_PYTHONPATH' in os.environ:
+ paths = [
+ os.path.abspath(item)
+ for item in os.environ['ROSE_PYTHONPATH'].split(os.pathsep)
+ ]
+ paths.extend(sys.path)
+ sys.path = paths
+
+ if 'PYTHONPATH' in os.environ:
+ for item in os.environ['PYTHONPATH'].split(os.pathsep):
+ abspath = os.path.abspath(item)
+ if abspath in sys.path:
+ sys.path.remove(abspath)
+
+
+pythonpath_manip()
+
+
import argparse
from inspect import signature
-import os
from pathlib import Path
-import sys
from pkg_resources import (
DistributionNotFound,
diff --git a/metomi/rose/tests/test_rose.py b/metomi/rose/tests/test_rose.py
new file mode 100644
index 000000000..7f7e95346
--- /dev/null
+++ b/metomi/rose/tests/test_rose.py
@@ -0,0 +1,44 @@
+# Copyright (C) British Crown (Met Office) & Contributors.
+#
+# This file is part of Rose, a framework for meteorological suites.
+#
+# Rose 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.
+#
+# Rose 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 Rose. If not, see .
+"""Test metomi/rose/rose.py
+"""
+
+import os
+import sys
+
+from metomi.rose.rose import pythonpath_manip
+
+
+def test_pythonpath_manip(monkeypatch):
+ """pythonpath_manip removes items in PYTHONPATH from sys.path
+ and adds items from ROSE_PYTHONPATH
+ """
+ # If PYTHONPATH is set...
+ monkeypatch.setenv('PYTHONPATH', '/remove1:/remove2')
+ monkeypatch.setattr('sys.path', ['/leave-alone', '/remove1', '/remove2'])
+ pythonpath_manip()
+ # ... we don't change PYTHONPATH
+ assert os.environ['PYTHONPATH'] == '/remove1:/remove2'
+ # ... but we do remove PYTHONPATH items from sys.path, and don't remove
+ # items there not in PYTHONPATH
+ assert sys.path == ['/leave-alone']
+
+ # If ROSE_PYTHONPATH is set we retrieve its contents and
+ # add them to the sys.path:
+ monkeypatch.setenv('ROSE_PYTHONPATH', '/add1:/add2')
+ pythonpath_manip()
+ assert sys.path == ['/add1', '/add2', '/leave-alone']
diff --git a/tox.ini b/tox.ini
index 8dad1c445..83a93c182 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,3 +13,5 @@ ignore=
E731,
# no longer best practice:
W503
+ ; module level import not at top of file
+ E402,