diff --git a/tests/test_notify_completion.py b/tests/test_notify_completion.py index ca536a58..981134fb 100644 --- a/tests/test_notify_completion.py +++ b/tests/test_notify_completion.py @@ -349,11 +349,14 @@ class TestFireNotifyTargets: mock_deliver.assert_not_called() @patch("turnstone.server._deliver_notification") - def test_empty_content_skipped(self, mock_deliver): + def test_empty_content_delivers_fallback(self, mock_deliver): + """Empty content should still deliver with a fallback message.""" ws = MagicMock() ws.notify_targets = '[{"channel_type":"discord","channel_id":"1"}]' _fire_notify_targets(ws, "") - mock_deliver.assert_not_called() + mock_deliver.assert_called_once() + payload = mock_deliver.call_args[0][1] + assert "no output captured" in payload["message"] @patch("turnstone.server._deliver_notification") def test_invalid_json_targets_skipped(self, mock_deliver): diff --git a/turnstone/server.py b/turnstone/server.py index 6ee43d2d..bb211e6c 100644 --- a/turnstone/server.py +++ b/turnstone/server.py @@ -1735,8 +1735,10 @@ def _extract_last_assistant_content(session: Any) -> str: def _fire_notify_targets(ws: Any, content: str) -> None: """Send completion notifications to all configured targets.""" - if not content or not ws.notify_targets: + if not ws.notify_targets: return + if not content: + content = "(Task completed — no output captured)" try: targets = json.loads(ws.notify_targets) @@ -2032,7 +2034,7 @@ async def create_workstream(request: Request) -> JSONResponse: def _run_initial() -> None: try: session.send(initial_message) - except Exception: + except BaseException: if isinstance(ws.ui, WebUI): ws.ui.on_stream_end() ws.ui.on_state_change("idle")