Placeholder text leaking to users and cron targets

NousResearch/hermes-agent · cluster 2234 · agent

Data as of

Nothing open on GitHub. Fix merged in #2442. Check it covers your case.

The short answer

0open issues
0open PRs
13items grouped
Known?
Yes. 13 related items grouped here, 4 issues already closed.
Fix event
A fix was merged to main in PR #2442. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PRs #2442, #11021. That is separate from the fix event.
Still open
Nothing open on GitHub.

Multiple bug reports and fix attempts address a problem where internal fallback strings like '(No response generated)' or '(empty)' are delivered to end users and cron job targets instead of being suppressed. The cluster covers both the detection of empty/think-only LLM responses and the separation of logging placeholders from actual delivery responses.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #2442 fix(cron): silent jobs return empty response for delivery skip

    merged to main

    files: cron/scheduler.py, tests/cron/test_scheduler.py

    Show the diff
    • 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)
    • tests/cron/test_scheduler.py
      @@ -234,6 +234,47 @@ def test_run_job_passes_session_db_and_cron_platform(self, tmp_path):
               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",
  • #11021 fix: empty response nudge crash + placeholder leak to cron targets

    merged to main

    files: cron/scheduler.py, gateway/run.py, run_agent.py, scripts/release.py

    Show the diff
    • cron/scheduler.py
      @@ -837,6 +837,9 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
                   )
       
               final_response = result.get("final_response", "") or ""
      +        # Strip leaked placeholder text that upstream may inject on empty completions.
      +        if final_response.strip() == "(No response generated)":
      +            final_response = ""
               # 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)"
    • gateway/run.py
      @@ -8886,7 +8886,7 @@ def _approval_notify_sync(approval_data: dict) -> None:
                   _resolved_model = getattr(_agent, "model", None) if _agent else None
       
                   if not final_response:
      -                error_msg = f"⚠️ {result['error']}" if result.get("error") else "(No response generated)"
      +                error_msg = f"⚠️ {result['error']}" if result.get("error") else ""
                       return {
                           "final_response": error_msg,
                           "messages": result.get("messages", []),
    • run_agent.py
      @@ -10945,8 +10945,9 @@ def _stop_spinner():
                                   #   tool(result) → assistant("(empty)") → user(nudge)
                                   # Without this, we'd have tool → user which most
                                   # APIs reject as an invalid sequence.
      -                            assistant_msg["content"] = "(empty)"
      -                            messages.append(assistant_msg)
      +                            _nudge_msg = self._build_assistant_message(assistant_message, finish_reason)
      +                            _nudge_msg["content"] = "(empty)"
      +                            messages.append(_nudge_msg)
                                   messages.append({
                                       "role": "user",
                                       "content": (
    • scripts/release.py
      @@ -179,6 +179,7 @@
           "[redacted:email]": "limars874",
           "[redacted:email]": "lesterli",
           "[redacted:email]": "MagicRay1217",
      +    "[redacted:email]": "GenKoKo",
           "[redacted:email]": "spniyant",
           "[redacted:email]": "olafthiele",
           "[redacted:email]": "sprmn24",

7 PRs closed without merging

From the close comments: done elsewhere 2 · merged via another change 2 · closed without comment 1 · other 2

  • #2238 fix: separate cron log placeholder from delivery response · other → #1741
  • #2241 fix(cron): separate log placeholder from delivery response for silent cron jobs · done elsewhere → #2442
  • #2255 fix(cron): treat empty agent response as silent instead of delivering fallback text · other → #2442
  • #2280 fix(cron): silent jobs return empty response for delivery skip · merged via another change → #2442
  • #2407 fix: separate cron log placeholder from delivery response (salvage #2238) · closed without comment
Show 2 more
  • #9292 fix: stop leaking placeholder text to users and cron targets · merged via another change → #11021
  • #11563 bug: literal (empty) string sent to users when model returns no content · done elsewhere → #11539
Closed issues · 4
  • Closed#2234bug(cron): silent cron jobs deliver "(No response generated)" to Discord
  • Closed#6559[Bug] Ollama concurrency limit causes literal '(empty)' responses on Telegram
  • Closed#9270Empty LLM responses get replaced with literal "(No response generated)" and delivered to users, including cron targets
  • Closed#11539bug: literal (empty) string sent to users when model returns no content
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.13: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
agent
Merged PRs
#2442, #11021
Closed issues
4
Attempts
7 PRs closed without merging
Fix link checked
16 Apr 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.

For your agent

The same facts, typed.