From f9e6067d2eee39a38363034c35686abec0afb6da Mon Sep 17 00:00:00 2001 From: Andrew White Date: Thu, 6 Aug 2026 16:51:04 -0500 Subject: [PATCH] fix: poll job.status() once per job in Experiment.status() Experiment.status() called job.status(runner=...) twice while building one status row: once for the display text and again for the returned dictionary. A backend failure therefore produced two identical ERROR tracebacks per status request and wasted a redundant status poll. Store the status in a local variable and reuse it for both the display string and the dict. Add a regression test that asserts job.status() is called exactly once for both return_dict=True and the default print path. Signed-off-by: Andrew White --- nemo_run/run/experiment.py | 7 +++---- test/run/test_experiment.py | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/nemo_run/run/experiment.py b/nemo_run/run/experiment.py index ba430413..791ffc7e 100644 --- a/nemo_run/run/experiment.py +++ b/nemo_run/run/experiment.py @@ -908,10 +908,9 @@ def _get_job_info_and_dict( idx: int, job: Job | JobGroup ) -> tuple[list[str], dict[str, str]]: job_info = [] + job_status = job.status(runner=self._runner) job_info.append(f"[bold green]Task {idx}[/bold green]: [bold orange1]{job.id}") - job_info.append( - f"- [bold green]Status[/bold green]: {str(job.status(runner=self._runner))}" - ) + job_info.append(f"- [bold green]Status[/bold green]: {str(job_status)}") job_info.append(f"- [bold green]Executor[/bold green]: {job.executor.info()}") try: @@ -927,7 +926,7 @@ def _get_job_info_and_dict( ] job_dict = { "name": job.id, - "status": job.status(runner=self._runner), + "status": job_status, "executor": job.executor.info(), "job_id": app_id, "handle": job.handle, diff --git a/test/run/test_experiment.py b/test/run/test_experiment.py index 4f160237..f58448d2 100644 --- a/test/run/test_experiment.py +++ b/test/run/test_experiment.py @@ -656,6 +656,30 @@ def test_experiment_status(mock_get_runner, temp_dir): mock_print.assert_called() +@patch("nemo_run.run.experiment.get_runner") +def test_experiment_status_polls_job_once(mock_get_runner, temp_dir): + """Experiment.status() should call job.status() once per job. + + Previously the display text and returned dict each called job.status(), + producing duplicate backend requests (and duplicate error logs on failure). + """ + mock_runner = MagicMock() + mock_get_runner.return_value = mock_runner + + with Experiment("test-exp") as exp: + task = run.Partial(dummy_function, x=1, y=2) + exp.add(task, name="test-job") + exp.jobs[0].status = MagicMock(return_value=AppState.SUCCEEDED) + + exp.status(return_dict=True) + exp.jobs[0].status.assert_called_once_with(runner=mock_runner) + + exp.jobs[0].status.reset_mock() + with patch.object(exp.console, "print"): + exp.status() + exp.jobs[0].status.assert_called_once_with(runner=mock_runner) + + @patch("nemo_run.run.experiment.get_runner") def test_experiment_cancel(mock_get_runner, temp_dir): """Test cancelling an experiment job."""