Show Utility Model rename failures
Notify users when automatic chat renaming cannot reach the Utility Model, while keeping the rename task best-effort. Normalize saved generated titles, mark WebUI state dirty after successful saves, and cover the success and failure paths with focused tests.
Alessandro committed
Jun 28, 2026 at 18:46 UTC
0b38258d6cfec6bb3d68d28d000f3c3aea68f14e
3 files changed
+89
-6
extensions/python/monologue_start/AGENTS.md
+2
-1
@@ -12,10 +12,11 @@
12
13
- Keep automatic rename behavior bounded and non-destructive.
14
- Do not override explicit user chat names without the intended guard conditions.
15
+- Surface Utility Model rename failures with one scoped error notification per chat.
16
17
## Work Guidance
18
18
-- Coordinate rename behavior with chat persistence and WebUI refresh.
19
+- Coordinate rename behavior with chat persistence and WebUI refresh after successful saves.
20
21
## Verification
22
extensions/python/monologue_start/_60_rename_chat.py
+26
-5
@@ -1,5 +1,7 @@
1
from helpers import persist_chat, tokens
2
from helpers.extension import Extension
3
+from helpers.notification import NotificationManager, NotificationPriority, NotificationType
4
+from helpers.state_monitor_integration import mark_dirty_all
5
from agent import LoopData
6
import asyncio
7
@@ -29,16 +31,35 @@ class RenameChat(Extension):
31
"fw.rename_chat.msg.md", current_name=current_name, history=history_text
32
)
33
# call utility model
32
- new_name = await self.agent.call_utility_model(
33
- system=system, message=message, background=True
34
- )
34
+ try:
35
+ new_name = await self.agent.call_utility_model(
36
+ system=system, message=message, background=True
37
+ )
38
+ except Exception:
39
+ NotificationManager.send_notification(
40
+ type=NotificationType.ERROR,
41
+ priority=NotificationPriority.NORMAL,
42
+ title="Chat Rename Failed",
43
+ message="Automatic chat renaming failed because the Utility Model was not reachable.",
44
+ detail=(
45
+ "Automatic chat renaming uses the Utility Model. Check Settings > Models > "
46
+ "Utility Model, provider/API key, and network reachability."
47
+ ),
48
+ display_time=10,
49
+ group="chat_rename",
50
+ id=f"chat_rename_failed_{self.agent.context.id}",
51
+ )
52
+ return
53
# update name
54
if new_name:
37
- # trim name to max length if needed
55
+ new_name = " ".join(str(new_name).split())
56
if len(new_name) > 40:
57
new_name = new_name[:40] + "..."
58
+ if not new_name:
59
+ return
60
# apply to context and save
61
self.agent.context.name = new_name
62
persist_chat.save_tmp_chat(self.agent.context)
43
- except Exception as e:
63
+ mark_dirty_all(reason="monologue_start.RenameChat.change_name")
64
+ except Exception:
65
pass # non-critical
tests/test_chat_rename_extension.py
new
+61
@@ -0,0 +1,61 @@
1
+from types import SimpleNamespace
2
+
3
+import pytest
4
+
5
+from extensions.python.monologue_start import _60_rename_chat as rename_chat
6
+from plugins._model_config.helpers import model_config
7
+
8
+
9
+pytestmark = pytest.mark.asyncio
10
+
11
+
12
+class _History:
13
+ def output_text(self) -> str:
14
+ return "User: Please help me plan the launch."
15
+
16
+
17
+class _Agent:
18
+ def __init__(self, *, response: str | None = None, error: Exception | None = None):
19
+ self.context = SimpleNamespace(id="ctx-rename", name="")
20
+ self.history = _History()
21
+ self._response = response
22
+ self._error = error
23
+
24
+ def read_prompt(self, name: str, **kwargs) -> str:
25
+ return name
26
+
27
+ async def call_utility_model(self, **kwargs) -> str:
28
+ if self._error:
29
+ raise self._error
30
+ return self._response or ""
31
+
32
+
33
+async def test_rename_failure_sends_utility_model_error_notification(monkeypatch):
34
+ sent: list[dict] = []
35
+
36
+ monkeypatch.setattr(model_config, "get_utility_model_config", lambda agent: {"ctx_length": 1000})
37
+ monkeypatch.setattr(rename_chat.NotificationManager, "send_notification", lambda **kwargs: sent.append(kwargs))
38
+
39
+ await rename_chat.RenameChat(agent=_Agent(error=RuntimeError("offline"))).change_name()
40
+
41
+ assert len(sent) == 1
42
+ assert sent[0]["type"] == rename_chat.NotificationType.ERROR
43
+ assert sent[0]["title"] == "Chat Rename Failed"
44
+ assert "Utility Model was not reachable" in sent[0]["message"]
45
+ assert sent[0]["id"] == "chat_rename_failed_ctx-rename"
46
+
47
+
48
+async def test_successful_rename_saves_clean_name_and_marks_state_dirty(monkeypatch):
49
+ saved_names: list[str] = []
50
+ dirty_reasons: list[str | None] = []
51
+
52
+ monkeypatch.setattr(model_config, "get_utility_model_config", lambda agent: {"ctx_length": 1000})
53
+ monkeypatch.setattr(rename_chat.persist_chat, "save_tmp_chat", lambda context: saved_names.append(context.name))
54
+ monkeypatch.setattr(rename_chat, "mark_dirty_all", lambda *, reason=None: dirty_reasons.append(reason))
55
+
56
+ agent = _Agent(response="\n\nLaunch Readiness Notes\n")
57
+ await rename_chat.RenameChat(agent=agent).change_name()
58
+
59
+ assert agent.context.name == "Launch Readiness Notes"
60
+ assert saved_names == ["Launch Readiness Notes"]
61
+ assert dirty_reasons == ["monologue_start.RenameChat.change_name"]