From 4b3e62cb95737239df4232d1da5c94eef37de662 Mon Sep 17 00:00:00 2001 From: labagent-maint-bot Date: Sat, 17 Oct 2026 03:30:56 -0700 Subject: [PATCH] fix: parse single-line fenced LLM replies in analyzer generate_script_with_ai stripped the opening markdown fence with str.index("\n"), which raises ValueError when the model returns a fenced reply on a single line (or a bare fence). Use find() and fall back to dropping the three backticks, matching the fix already applied to the parameter optimizer, paper-pilot client and skill generator. Adds parametrized regression tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lab_harness/analysis/analyzer.py | 5 +++-- tests/test_analyzer_ai_paths.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lab_harness/analysis/analyzer.py b/src/lab_harness/analysis/analyzer.py index 406cc65..a1dc627 100644 --- a/src/lab_harness/analysis/analyzer.py +++ b/src/lab_harness/analysis/analyzer.py @@ -143,8 +143,9 @@ def generate_script_with_ai( # Strip markdown fences if present if script.startswith("```"): - first_nl = script.index("\n") - script = script[first_nl + 1 :] + # Drop the opening fence line; a single-line reply has no newline to split on. + first_nl = script.find("\n") + script = script[first_nl + 1 :] if first_nl != -1 else script[3:] if script.endswith("```"): script = script[:-3] script = script.strip() diff --git a/tests/test_analyzer_ai_paths.py b/tests/test_analyzer_ai_paths.py index b61d2a7..a54e3ae 100644 --- a/tests/test_analyzer_ai_paths.py +++ b/tests/test_analyzer_ai_paths.py @@ -78,6 +78,16 @@ def test_markdown_fences_are_stripped(self, llm_on, tmp_path: Path, raw: str): script = Analyzer().generate_script_with_ai(tmp_path / "x.csv", "IV") assert script == "import numpy\nprint(1)" + @pytest.mark.parametrize( + ("raw", "expected"), + [("```print(1)```", "print(1)"), ("```", ""), ("```python\n```", "")], + ids=["single-line", "bare", "empty-block"], + ) + def test_fences_without_newline_do_not_raise(self, llm_on, tmp_path: Path, raw: str, expected: str): + """A fenced reply with no newline after the opening fence must not raise ValueError.""" + llm_on.return_value = _router(raw) + assert Analyzer().generate_script_with_ai(tmp_path / "x.csv", "IV") == expected + class TestInterpretResults: def test_data_path_preview_in_context(self, llm_on, tmp_path: Path):