diff --git a/src/dstack/_internal/cli/services/presets/create.py b/src/dstack/_internal/cli/services/presets/create.py index 5fb29b4e4..e4427800a 100644 --- a/src/dstack/_internal/cli/services/presets/create.py +++ b/src/dstack/_internal/cli/services/presets/create.py @@ -410,12 +410,14 @@ def create_preset( allowed_fleets: Optional[tuple[str, ...]] = None, previous: Sequence[PresetSession] = (), ) -> PresetCreateResult: + # Resolve caller-local inputs before creating or changing session state. A + # preflight error while resuming must leave the existing session resumable. + resolved_configuration = _resolve_preset_env(configuration) session = resume_session or create_preset_session( configuration, previous=tuple(session.preset_id for session in previous), ) try: - resolved_configuration = _resolve_preset_env(configuration) # A creation session runs unattended for hours; an idling machine would # freeze the agent and the process supervising it alike. with prevent_idle_sleep(): diff --git a/src/tests/_internal/cli/services/presets/test_create.py b/src/tests/_internal/cli/services/presets/test_create.py index e3ae148d8..bfe7eb99f 100644 --- a/src/tests/_internal/cli/services/presets/test_create.py +++ b/src/tests/_internal/cli/services/presets/test_create.py @@ -49,7 +49,7 @@ create_agent_workspace, remove_agent_workspace, ) -from dstack._internal.core.errors import CLIError +from dstack._internal.core.errors import CLIError, ConfigurationError from dstack._internal.core.models.configurations import PresetConfiguration from dstack._internal.core.models.envs import EnvSentinel from dstack._internal.core.models.runs import Run, RunStatus @@ -175,6 +175,29 @@ async def create(**kwargs): assert state["id"] == paths[0].name assert "testing preset" in (paths[0] / "agent.log").read_text() + def test_resume_preflight_error_leaves_session_interrupted(self, tmp_path, monkeypatch): + monkeypatch.delenv("MISSING_PRESET_VALUE", raising=False) + session_dir = tmp_path / "ab12cd34" + session_dir.mkdir() + session = PresetSession(path=session_dir, preset_id="ab12cd34") + session.write_state(get_session_state(status="interrupted", run=get_session_run())) + + with pytest.raises(ConfigurationError, match="MISSING_PRESET_VALUE"): + create_preset( + api=SimpleNamespace(), + configuration=PresetConfiguration( + name="qwen", + base="Qwen/Qwen3.5-27B", + env=["MISSING_PRESET_VALUE"], + ), + store=PresetStore(tmp_path / "presets"), + resume_session=session, + ) + + state = session.read_state() + assert state is not None + assert state.status == "interrupted" + def test_finalization_error_does_not_mask_success(self, tmp_path, monkeypatch, capsys): monkeypatch.setenv("HOME", str(tmp_path)) monkeypatch.setenv("USERPROFILE", str(tmp_path))