From cda7a921a43103bc0bd19a96f42d8b84ad815538 Mon Sep 17 00:00:00 2001 From: Marsel Safin <179933638+marcelsafin@users.noreply.github.com> Date: Mon, 3 Aug 2026 21:30:55 +0200 Subject: [PATCH] fix(workflows): reject mismatched run state IDs (#3899) Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/specify_cli/workflows/engine.py | 5 +++++ tests/test_workflows.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/specify_cli/workflows/engine.py b/src/specify_cli/workflows/engine.py index 459e95ac4..a478aafdd 100644 --- a/src/specify_cli/workflows/engine.py +++ b/src/specify_cli/workflows/engine.py @@ -761,6 +761,11 @@ class RunState: "Invalid run state: missing required field(s): " + ", ".join(missing_fields) ) + if state_data["run_id"] != run_id: + raise ValueError( + f"Invalid run state: stored run_id {state_data['run_id']!r} " + f"does not match requested run_id {run_id!r}" + ) workflow_id = state_data["workflow_id"] if not isinstance(workflow_id, str) or not _ID_PATTERN.fullmatch( diff --git a/tests/test_workflows.py b/tests/test_workflows.py index a1b3f6bd3..b9cfd67c0 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -7053,6 +7053,35 @@ class TestRunState: with pytest.raises(FileNotFoundError): RunState.load("nonexistent", project_dir) + def test_load_rejects_stored_run_id_mismatch(self, project_dir): + """The state payload cannot redirect later writes to another run.""" + from specify_cli.workflows.engine import RunState + + run_dir = ( + project_dir + / ".specify" + / "workflows" + / "runs" + / "requested-run" + ) + run_dir.mkdir(parents=True) + (run_dir / "state.json").write_text( + json.dumps( + { + "run_id": "other-run", + "workflow_id": "test-workflow", + "status": "created", + } + ), + encoding="utf-8", + ) + + with pytest.raises( + ValueError, + match="stored run_id 'other-run' does not match requested run_id 'requested-run'", + ): + RunState.load("requested-run", project_dir) + @pytest.mark.parametrize( ("installed_workflow_id", "installed_registry_root"), [