From 2b538c1f4ede4dab2991ebe17f8347e257c3f323 Mon Sep 17 00:00:00 2001 From: vanthinh6886 <89525629+vanthinh6886@users.noreply.github.com> Date: Mon, 18 May 2026 20:03:38 -0700 Subject: [PATCH] fix: guard json.loads() against invalid TTS and skill_view responses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two code paths call json.loads() on output from external tools without catching JSONDecodeError. If the tool returns a non-JSON string (error message, empty string, or None), the entire call path crashes. 1. gateway/run.py — text_to_speech_tool() result in voice reply path. A TTS failure that returns an error string instead of JSON crashes the voice reply handler, killing the message response entirely. 2. cron/scheduler.py — skill_view() result when loading skills for cron jobs. A corrupted or missing skill file that returns an error string instead of JSON crashes the cron tick, preventing all jobs from executing that cycle. Both fixes catch (json.JSONDecodeError, TypeError), log a warning, and gracefully skip the failed operation instead of crashing. --- cron/scheduler.py | 7 ++++++- gateway/run.py | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cron/scheduler.py b/cron/scheduler.py index 9a1f3d1bfe5..c6941c763d8 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -1034,7 +1034,12 @@ def _build_job_prompt(job: dict, prerun_script: Optional[tuple] = None) -> str: parts = [] skipped: list[str] = [] for skill_name in skill_names: - loaded = json.loads(skill_view(skill_name)) + try: + loaded = json.loads(skill_view(skill_name)) + except (json.JSONDecodeError, TypeError): + logger.warning("Cron job '%s': skill '%s' returned invalid JSON, skipping", job.get("name", job.get("id")), skill_name) + skipped.append(skill_name) + continue if not loaded.get("success"): error = loaded.get("error") or f"Failed to load skill '{skill_name}'" logger.warning("Cron job '%s': skill not found, skipping — %s", job.get("name", job.get("id")), error) diff --git a/gateway/run.py b/gateway/run.py index b98857ffe52..08805076714 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -10592,7 +10592,11 @@ class GatewayRunner: result_json = await asyncio.to_thread( text_to_speech_tool, text=tts_text, output_path=audio_path ) - result = json.loads(result_json) + try: + result = json.loads(result_json) + except (json.JSONDecodeError, TypeError): + logger.warning("Auto voice reply TTS returned invalid JSON: %s", result_json[:200] if result_json else result_json) + return # Use the actual file path from result (may differ after opus conversion) actual_path = result.get("file_path", audio_path)