diff --git a/cron/scheduler.py b/cron/scheduler.py index 3108ff3ad..fe004beb1 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -412,9 +412,10 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]: result = agent.run_conversation(prompt) - final_response = result.get("final_response", "") - if not final_response: - final_response = "(No response generated)" + final_response = result.get("final_response", "") or "" + # Use a separate variable for log display; keep final_response clean + # for delivery logic (empty response = no delivery). + logged_response = final_response if final_response else "(No response generated)" output = f"""# Cron Job: {job_name} @@ -428,7 +429,7 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]: ## Response -{final_response} +{logged_response} """ logger.info("Job '%s' completed successfully", job_name) diff --git a/tests/cron/test_scheduler.py b/tests/cron/test_scheduler.py index 970df05c9..3a453ddcc 100644 --- a/tests/cron/test_scheduler.py +++ b/tests/cron/test_scheduler.py @@ -234,6 +234,47 @@ class TestRunJobSessionPersistence: assert kwargs["session_id"].startswith("cron_test-job_") fake_db.close.assert_called_once() + def test_run_job_empty_response_returns_empty_not_placeholder(self, tmp_path): + """Empty final_response should stay empty for delivery logic (issue #2234). + + The placeholder '(No response generated)' should only appear in the + output log, not in the returned final_response that's used for delivery. + """ + job = { + "id": "silent-job", + "name": "silent test", + "prompt": "do work via tools only", + } + fake_db = MagicMock() + + with patch("cron.scheduler._hermes_home", tmp_path), \ + patch("cron.scheduler._resolve_origin", return_value=None), \ + patch("dotenv.load_dotenv"), \ + patch("hermes_state.SessionDB", return_value=fake_db), \ + patch( + "hermes_cli.runtime_provider.resolve_runtime_provider", + return_value={ + "api_key": "test-key", + "base_url": "https://example.invalid/v1", + "provider": "openrouter", + "api_mode": "chat_completions", + }, + ), \ + patch("run_agent.AIAgent") as mock_agent_cls: + mock_agent = MagicMock() + # Agent did work via tools but returned no text + mock_agent.run_conversation.return_value = {"final_response": ""} + mock_agent_cls.return_value = mock_agent + + success, output, final_response, error = run_job(job) + + assert success is True + assert error is None + # final_response should be empty for delivery logic to skip + assert final_response == "" + # But the output log should show the placeholder + assert "(No response generated)" in output + def test_run_job_sets_auto_delivery_env_from_dotenv_home_channel(self, tmp_path, monkeypatch): job = { "id": "test-job",