Title generator leaking think/reasoning blocks from LLM output
NousResearch/hermes-agent · cluster 18529 · agent
Data as of
Nothing open on GitHub. Fix merged in #56298. Check it covers your case.
The short answer
- Known?
- Yes. 9 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #56298. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
unresolved. That is separate from the fix event. - Still open
- Nothing open on GitHub.
Multiple fixes address the title generator reading raw LLM output that includes <think> or <reasoning> blocks instead of using the existing extract_content_or_reasoning() helper. One member also adds multi-message context, noise filtering, retry logic, and provider override to title generation.
Open issues0
Open PRs0
Fixes on record
-
#56298 fix(title_generator): strip think blocks from LLM output before extracting title
files:
agent/title_generator.py,scripts/release.py,tests/agent/test_title_generator.pyShow the diff
-
agent/title_generator.py@@ -87,7 +87,15 @@ def generate_title( timeout=timeout, main_runtime=main_runtime, ) - title = (response.choices[0].message.content or "").strip() + content = response.choices[0].message.content or "" + # Strip thinking/reasoning blocks that think-enabled models + # (MiniMax M2.7, DeepSeek, etc.) emit even for simple prompts like + # title generation. Without this the raw <think>...</think> XML + # leaks into session titles. Reuses the canonical scrubber so all + # tag variants (unterminated blocks, orphan closes, mixed case) + # are handled, not just a single literal <think> pair. + from agent.agent_runtime_helpers import strip_think_blocks + title = strip_think_blocks(None, content).strip() # Clean up: remove quotes, trailing punctuation, prefixes like "Title: " title = title.strip('"\'') if title.lower().startswith("title:"): -
scripts/release.py@@ -48,6 +48,7 @@ "[redacted:email]": "bitcryptic-gw", # PR #53997 salvage (Matrix E2EE: resolve device_id via query_keys({mxid: []}) when whoami returns none; guard verification call sites so query_keys is never sent [null]; reset _device_id_unverified at connect() start; disconnect before reconnect) "[redacted:email]": "abchiaravalle", # PR #46997 salvage (recover resume_pending sessions: dual freshness signal + empty-turn safety net so restart auto-resume never sends a blank user turn) "[redacted:email]": "swissly", # PR #47167 salvage (wrap cron delivery thread-pool fallback in its own try/except so a per-target failure can't escape the except-RuntimeError block and crash the multi-target delivery loop; #47163) + "[redacted:email]": "shawchanshek", # PR #44126 salvage (strip <think>...</think> reasoning blocks from title-generator LLM output via the canonical strip_think_blocks scrubber so reasoning-model output can't leak into session titles) "[redacted:email]": "YLChen-007", # PR #27289 salvage (case-insensitive streaming reasoning-tag filter in cli.py _stream_delta + gateway stream_consumer so mixed-case variants like <Think>/<ThInK> are suppressed, not just the hardcoded case literals) "[redacted:email]": "kangsoo-bit", # PR #47508 salvage (keep Telegram gateway alive on transient bootstrap network errors: best-effort deleteWebhook + resilient start_polling degrade to background recovery instead of failing startup) "[redacted:email]": "testingbuddies24", # PR #43192 salvage (strip orphan think-tag close tags in progressive gateway stream so a bare </think> whose open was dropped upstream can't leak to the user) -
tests/agent/test_title_generator.py@@ -68,6 +68,37 @@ def test_strips_quotes(self): title = generate_title("how do I set up docker", "First install...") assert title == "Setting Up Docker Environment" + def test_strips_think_blocks(self): + """Reasoning-model output wrapped in <think>...</think> must not + leak into the session title.""" + mock_response = MagicMock() + mock_response.choices = [MagicMock()] + mock_response.choices[0].message.content = ( + "<think>The user wants a title. I'll summarize the topic " + "concisely.</think>Debugging Python Import Errors" + ) + + with patch("agent.title_generator.call_llm", return_value=mock_response): + title = generate_title("help me fix this import", "Sure...") + assert title == "Debugging Python Import Errors" + assert "<think>" not in title + assert "summarize" not in title + + def test_strips_unterminated_think_block(self): + """An unterminated <think> block (no close tag) must still be + stripped so the leaked reasoning doesn't become the title.""" + mock_response = MagicMock() + mock_response.choices = [MagicMock()] + mock_response.choices[0].message.content = ( + "<think>Let me reason about a good title for this session" + ) + + with patch("agent.title_generator.call_llm", return_value=mock_response): + title = generate_title("hello", "hi there") + # Everything from the unterminated open tag onward is stripped, + # leaving nothing → None. + assert title is None + def test_strips_title_prefix(self): mock_response = MagicMock() mock_response.choices = [MagicMock()]
-
7 PRs closed without merging
From the close comments: done elsewhere 2 · closed without comment 1 · duplicate 1 · merged via another change 1 · superseded by another PR 1 · other 1
-
#18537 Fix/title generation thinking leak · done elsewhere →
3b739b9,#18529 -
#18643 fix: use extract_content_or_reasoning() for title generation with reasoning models · other →
#17797,#20266 -
#19008 fix(title): strip thinking blocks from auto-generated session titles · merged via another change →
3b739b9,#44126,#56298 -
#20266 fix: support reasoning models in title generation · done elsewhere →
3b739b9,#18529 -
#22568 fix(title-generator): strip <think>/<reasoning> blocks before saving session title · duplicate →
#19008,#18529
Closed issues · 1
- Closed#18529[Bug]: title_generator.py read response.choices[0].message.content directly instead of use extract_content_or_reasoning() to extract <thinking> tag
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.22: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means 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 LabAt a glance
- Subsystem
- agent
- Merged PRs
- #56298
- Closed issues
- 1
- Attempts
- 7 PRs closed without merging
- Fix link checked
- 1 Jul 2026