diff --git a/carwatch/grounding.py b/carwatch/grounding.py
index 4999169..b3e03dd 100644
--- a/carwatch/grounding.py
+++ b/carwatch/grounding.py
@@ -52,6 +52,12 @@
_THINK_BLOCK = re.compile(r"<(think|thinking|reasoning)>.*?\1>",
re.IGNORECASE | re.DOTALL)
+# A reasoning block whose OPENING tag was never in the output: chat templates
+# that start the model inside (Qwen3.6 on the VTA, 27 Sep 2026) make
+# it emit "...reasoning... answer", or just " answer" when the
+# reasoning went to a separate field. The paired pattern above cannot see it,
+# and the bare tag was posted to the room and handed to the voice.
+_THINK_CLOSE = re.compile(r"(think|thinking|reasoning)>", re.IGNORECASE)
_BULLET = re.compile(r"^\s*(?:[-*\u2022]|\d+[.)])\s+")
@@ -68,6 +74,11 @@ def strip_scaffold(text: str) -> str:
if not text:
return text
cleaned = _THINK_BLOCK.sub("", text).strip()
+ closes = list(_THINK_CLOSE.finditer(cleaned))
+ if closes:
+ after = cleaned[closes[-1].end():].strip()
+ if after:
+ cleaned = after
lines = cleaned.splitlines()
i, dropping = 0, False
while i < len(lines):
diff --git a/tests/test_scaffold_leak.py b/tests/test_scaffold_leak.py
index 5a0f1b6..7d978fd 100644
--- a/tests/test_scaffold_leak.py
+++ b/tests/test_scaffold_leak.py
@@ -58,5 +58,22 @@ def test_prompt_and_stripper_share_the_headings(self):
self.assertIn(CANNOT_HEADING, p)
+
+class StrayThinkClose(unittest.TestCase):
+ """VTA 27 Sep: ' Kuule, Petrus, ...' reached the room and the voice."""
+
+ def test_bare_closing_tag_is_dropped(self):
+ self.assertEqual(strip_scaffold(" Kuule, Petrus."), "Kuule, Petrus.")
+
+ def test_reasoning_before_unopened_close_is_dropped(self):
+ self.assertEqual(strip_scaffold("The user greets me.\nI should reply.\nHello Petrus!"),
+ "Hello Petrus!")
+
+ def test_paired_block_still_works(self):
+ self.assertEqual(strip_scaffold("hmmHi."), "Hi.")
+
+ def test_only_reasoning_is_not_emptied(self):
+ self.assertTrue(strip_scaffold("thinking only").strip())
+
if __name__ == "__main__":
unittest.main()