diff --git a/CHANGES.md b/CHANGES.md
index cdc413c641..ce48c8120c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -13,6 +13,13 @@ ones in. -->
## 2.1.0 (Released 2023-07-21)
+### 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`.
+
### Fixes
[#2699](https://github.com/metomi/rose/pull/2699) -
diff --git a/metomi/rose/rose.py b/metomi/rose/rose.py
index be4606cffb..b167c40f8e 100644
--- a/metomi/rose/rose.py
+++ b/metomi/rose/rose.py
@@ -14,11 +14,39 @@
#
# 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 Cylc Environment
+
+ * Remove PYTHONPATH items from sys.path to prevent PYTHONPATH
+ contaminating the Cylc 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:
+ for item in os.environ['ROSE_PYTHONPATH'].split(os.pathsep):
+ print(f'extracted {item} from ROSE_PYTHONPATH')
+ abspath = os.path.abspath(item)
+ sys.path.insert(0, abspath)
+ 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 0000000000..81745fd568
--- /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', '/remove-from-sys.path')
+ monkeypatch.setattr('sys.path', ['/leave-alone', '/remove-from-sys.path'])
+ pythonpath_manip()
+ # ... we don't change PYTHONPATH
+ assert os.environ['PYTHONPATH'] == '/remove-from-sys.path'
+ # ... but we do remove PYTHONPATH items from sys.path, and don't remove
+ # items there not in PYTHONPATH
+ assert sys.path == ['/leave-alone']
+
+ # If CYLC_PYTHONPATH is set we retrieve its contents and
+ # add them to the sys.path:
+ monkeypatch.setenv('ROSE_PYTHONPATH', '/add-to-sys.path')
+ pythonpath_manip()
+ assert sys.path == ['/add-to-sys.path', '/leave-alone']
diff --git a/t/rose-ana/00-run-basic/flow.cylc b/t/rose-ana/00-run-basic/flow.cylc
index 0b35076ef9..b0d4be2692 100644
--- a/t/rose-ana/00-run-basic/flow.cylc
+++ b/t/rose-ana/00-run-basic/flow.cylc
@@ -4,7 +4,7 @@ UTC mode=True
[[events]]
abort on stalled = True
mail events =
- timeout=PT30S
+ timeout=PT5S
[scheduling]
[[dependencies]]
diff --git a/tox.ini b/tox.ini
index 8dad1c4452..83a93c182c 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,