From 37b3a480abfaa1c07d73c01d540c5c255f9b60db Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 16 Oct 2026 20:10:21 -0400 Subject: [PATCH 1/4] fix(apps): bind volume mounts from RUNPOD_MOUNTS when the runtime did not vol.path reads bindings the worker runtime installs through _configure_mounts before user code. Runtime images published before that hook never call it, so on them every deployed NetworkVolume attached but vol.path raised "not mounted in the current execution". Deployed workers run the client's vendored runpod package, so the first vol.path lookup now installs the bindings from the RUNPOD_MOUNTS provisioning env itself when the runtime has not. A runtime that configured mounts still takes precedence. --- runpod/apps/volume.py | 26 ++++++++++++++++++++- tests/test_apps/test_volume.py | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/runpod/apps/volume.py b/runpod/apps/volume.py index 943c80b5..35579842 100644 --- a/runpod/apps/volume.py +++ b/runpod/apps/volume.py @@ -2,6 +2,7 @@ import logging import json +import os from abc import ABC, abstractmethod from collections.abc import Mapping from pathlib import Path, PurePosixPath @@ -43,6 +44,7 @@ def reference(self) -> str: @property def path(self) -> Path: """the unique mount path for this volume in the current worker.""" + _ensure_mounts_configured() paths = _mount_bindings.get((self.kind, self.reference), ()) if not paths: raise VolumeError( @@ -130,6 +132,27 @@ def normalize_mounts(mounts: Optional[Mapping[str, Volume]]) -> Dict[str, Volume _mount_bindings: Mapping[Tuple[str, str], Tuple[Path, ...]] = MappingProxyType({}) +_mounts_configured = False + + +def _ensure_mounts_configured() -> None: + """bind mounts from RUNPOD_MOUNTS if the worker runtime did not. + + the runtime calls _configure_mounts before user code, but runtime + images released before that hook never do. deployed workers run + this vendored package, so reading the provisioning env here keeps + vol.path working regardless of the image's runtime version. + """ + if _mounts_configured: + return + raw = os.environ.get("RUNPOD_MOUNTS") + if not raw: + return + try: + bindings = json.loads(raw) + except json.JSONDecodeError as exc: + raise VolumeError("RUNPOD_MOUNTS must contain a valid JSON array") from exc + _configure_mounts(bindings) def _configure_mounts(bindings: List[Dict[str, str]]) -> None: @@ -153,7 +176,8 @@ def _configure_mounts(bindings: List[Dict[str, str]]) -> None: paths = resolved.setdefault(key, []) if path not in paths: paths.append(path) - global _mount_bindings + global _mount_bindings, _mounts_configured + _mounts_configured = True _mount_bindings = MappingProxyType( {key: tuple(paths) for key, paths in resolved.items()} ) diff --git a/tests/test_apps/test_volume.py b/tests/test_apps/test_volume.py index 70c255db..3ad36829 100644 --- a/tests/test_apps/test_volume.py +++ b/tests/test_apps/test_volume.py @@ -109,6 +109,48 @@ def test_multiple_bindings_require_explicit_path(self): with pytest.raises(VolumeError): NetworkVolume("nv-1").path + def test_path_binds_from_env_when_runtime_did_not(self, monkeypatch): + # runtime images released before the mounts hook never call + # _configure_mounts; the vendored sdk reads the env itself + import runpod.apps.volume as volume_mod + + monkeypatch.setattr(volume_mod, "_mounts_configured", False) + monkeypatch.setattr(volume_mod, "_mount_bindings", {}) + monkeypatch.setenv( + "RUNPOD_MOUNTS", + json.dumps( + [ + { + "kind": "network", + "reference": "models", + "id": "nv-1", + "path": "/runpod-volume", + } + ] + ), + ) + assert str(NetworkVolume("models").path) == "/runpod-volume" + assert str(NetworkVolume("nv-1").path) == "/runpod-volume" + + def test_runtime_configuration_wins_over_env(self, monkeypatch): + monkeypatch.setenv( + "RUNPOD_MOUNTS", + json.dumps( + [{"kind": "network", "reference": "models", "id": "nv-1", "path": "/x"}] + ), + ) + # clean_mounts already configured an empty set, as the runtime does + with pytest.raises(VolumeError, match="not mounted"): + NetworkVolume("models").path + + def test_invalid_env_bindings_raise(self, monkeypatch): + import runpod.apps.volume as volume_mod + + monkeypatch.setattr(volume_mod, "_mounts_configured", False) + monkeypatch.setenv("RUNPOD_MOUNTS", "{not json") + with pytest.raises(VolumeError, match="valid JSON"): + NetworkVolume("models").path + def test_invalid_configuration_does_not_partially_publish(self, tmp_path): (tmp_path / "model").write_text("retained") binding = { From dee2d208d5854831d2d0a0c5613ebfb6290edac3 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 19 Oct 2026 23:09:08 -0400 Subject: [PATCH 2/4] fix(apps): publish mount bindings before marking mounts configured --- runpod/apps/volume.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/runpod/apps/volume.py b/runpod/apps/volume.py index 35579842..c25c004b 100644 --- a/runpod/apps/volume.py +++ b/runpod/apps/volume.py @@ -177,10 +177,12 @@ def _configure_mounts(bindings: List[Dict[str, str]]) -> None: if path not in paths: paths.append(path) global _mount_bindings, _mounts_configured - _mounts_configured = True + # publish the bindings before the flag: a thread that sees the flag + # must not read the previous, empty bindings _mount_bindings = MappingProxyType( {key: tuple(paths) for key, paths in resolved.items()} ) + _mounts_configured = True class VolumeResolver: From 8074253e7af65026995d129907d24c34149b5a61 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 20 Oct 2026 12:28:31 -0400 Subject: [PATCH 3/4] refactor(apps): name the env mount fallback for what it does --- runpod/apps/volume.py | 14 +++++++------- tests/test_apps/test_volume.py | 7 ++----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/runpod/apps/volume.py b/runpod/apps/volume.py index c25c004b..48709358 100644 --- a/runpod/apps/volume.py +++ b/runpod/apps/volume.py @@ -44,7 +44,7 @@ def reference(self) -> str: @property def path(self) -> Path: """the unique mount path for this volume in the current worker.""" - _ensure_mounts_configured() + _bind_mounts_from_env() paths = _mount_bindings.get((self.kind, self.reference), ()) if not paths: raise VolumeError( @@ -135,13 +135,13 @@ def normalize_mounts(mounts: Optional[Mapping[str, Volume]]) -> Dict[str, Volume _mounts_configured = False -def _ensure_mounts_configured() -> None: - """bind mounts from RUNPOD_MOUNTS if the worker runtime did not. +def _bind_mounts_from_env() -> None: + """bind mounts from RUNPOD_MOUNTS unless the worker runtime already did. - the runtime calls _configure_mounts before user code, but runtime - images released before that hook never do. deployed workers run - this vendored package, so reading the provisioning env here keeps - vol.path working regardless of the image's runtime version. + the runtime calls _configure_mounts before user code, but images + released before that hook never do. deployed workers run this + vendored package, so reading the provisioning env here keeps + vol.path working on those images. no-op without the env. """ if _mounts_configured: return diff --git a/tests/test_apps/test_volume.py b/tests/test_apps/test_volume.py index 3ad36829..45023e8d 100644 --- a/tests/test_apps/test_volume.py +++ b/tests/test_apps/test_volume.py @@ -6,6 +6,7 @@ import pytest +import runpod.apps.volume as volume_mod from runpod.apps.datacenter import DataCenter from runpod.apps.placement import PlacementError from runpod.apps.spec import ResourceKind, ResourceSpec @@ -112,8 +113,6 @@ def test_multiple_bindings_require_explicit_path(self): def test_path_binds_from_env_when_runtime_did_not(self, monkeypatch): # runtime images released before the mounts hook never call # _configure_mounts; the vendored sdk reads the env itself - import runpod.apps.volume as volume_mod - monkeypatch.setattr(volume_mod, "_mounts_configured", False) monkeypatch.setattr(volume_mod, "_mount_bindings", {}) monkeypatch.setenv( @@ -143,9 +142,7 @@ def test_runtime_configuration_wins_over_env(self, monkeypatch): with pytest.raises(VolumeError, match="not mounted"): NetworkVolume("models").path - def test_invalid_env_bindings_raise(self, monkeypatch): - import runpod.apps.volume as volume_mod - + def test_malformed_mounts_env_raises(self, monkeypatch): monkeypatch.setattr(volume_mod, "_mounts_configured", False) monkeypatch.setenv("RUNPOD_MOUNTS", "{not json") with pytest.raises(VolumeError, match="valid JSON"): From 14ced1ba2bf5acf21c83ea729cf6b5c99413d87c Mon Sep 17 00:00:00 2001 From: justinwlin <15874969+justinwlin@users.noreply.github.com> Date: Wed, 21 Oct 2026 18:53:25 +0000 Subject: [PATCH 4/4] test(apps): patch volume mount globals by dotted path Drops the second import of runpod.apps.volume that CodeQL flagged. The globals are module-level, so they are patched by name, not on NetworkVolume. --- tests/test_apps/test_volume.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_apps/test_volume.py b/tests/test_apps/test_volume.py index 45023e8d..0093ce2b 100644 --- a/tests/test_apps/test_volume.py +++ b/tests/test_apps/test_volume.py @@ -6,7 +6,6 @@ import pytest -import runpod.apps.volume as volume_mod from runpod.apps.datacenter import DataCenter from runpod.apps.placement import PlacementError from runpod.apps.spec import ResourceKind, ResourceSpec @@ -113,8 +112,8 @@ def test_multiple_bindings_require_explicit_path(self): def test_path_binds_from_env_when_runtime_did_not(self, monkeypatch): # runtime images released before the mounts hook never call # _configure_mounts; the vendored sdk reads the env itself - monkeypatch.setattr(volume_mod, "_mounts_configured", False) - monkeypatch.setattr(volume_mod, "_mount_bindings", {}) + monkeypatch.setattr("runpod.apps.volume._mounts_configured", False) + monkeypatch.setattr("runpod.apps.volume._mount_bindings", {}) monkeypatch.setenv( "RUNPOD_MOUNTS", json.dumps( @@ -143,7 +142,7 @@ def test_runtime_configuration_wins_over_env(self, monkeypatch): NetworkVolume("models").path def test_malformed_mounts_env_raises(self, monkeypatch): - monkeypatch.setattr(volume_mod, "_mounts_configured", False) + monkeypatch.setattr("runpod.apps.volume._mounts_configured", False) monkeypatch.setenv("RUNPOD_MOUNTS", "{not json") with pytest.raises(VolumeError, match="valid JSON"): NetworkVolume("models").path