From f9e4a50f80bd8d2a1ccd914e483b224077873511 Mon Sep 17 00:00:00 2001 From: Dave Wilding Date: Tue, 25 Aug 2026 20:42:24 +0800 Subject: [PATCH] Make output parsing robust to multi-line JSON and code blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The agent emitted IMPLEMENTATION_RESULT with a multi-line JSON object wrapped in a json code-block wrapping (in addition to the original single-line format). This is a safety net — the prompt should prevent it, but the parser should not fail if the agent formats the JSON differently. 2. Output contract: explicitly states the JSON must have exactly two fields (title and body), must not invent other fields, and must be on a single line without code-block wrapping. Added concrete examples of what not to do. --- .github/scripts/probe_issue.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/scripts/probe_issue.py b/.github/scripts/probe_issue.py index d460baf..53e4817 100644 --- a/.github/scripts/probe_issue.py +++ b/.github/scripts/probe_issue.py @@ -462,8 +462,8 @@ def test_deploy(charm, juju: jubilant.Juju): made all the right changes. **After `run_tox` passes for all modified charms, end your output with a line \ -that starts with `IMPLEMENTATION_RESULT:` followed by a JSON object with two \ -fields:** +that starts with `IMPLEMENTATION_RESULT:` followed by a JSON object with \ +exactly two fields:** - `title`: a compact PR title — a short phrase, not a full sentence. \ Examples: "Try foo in bar tests", "log_level filters DEBUG from captured \ @@ -474,7 +474,13 @@ def test_deploy(charm, juju: jubilant.Juju): Use proper markdown: headers (`##`), bullet points, code blocks (fenced \ with triple backticks), and paragraphs separated by blank lines. -For example: +**The JSON must have exactly these two fields — `title` and `body`. Do not \ +invent other fields** (no `charms_modified`, `claim_tested`, `run_tox_result`, \ +etc.). Put all your reasoning inside `body`. The workflow parses only `title` \ +and `body`; any other fields are silently discarded. + +**The JSON must be on a single line.** Do not wrap it in a code block. \ +Escape newlines inside `body` as `\\n`. For example: ``` IMPLEMENTATION_RESULT: {"title": "log_level filters DEBUG from captured logs", "body": "## Claims\\n\\n- **A**: log_level=INFO retains INFO logs in the captured section\\n- **B**: without it, DEBUG logs appear from log_file_level\\n\\nI believe the doc is correct. I added a test asserting ...\\n\\nIf CI passes, ... If CI fails, ..."} @@ -652,10 +658,12 @@ def parse_decision(output: str) -> dict[str, str]: raise ValueError("IMPLEMENTATION_BLOCKER must not be empty.") return {"decision": "BLOCKED", "blocker": blocker} + # The agent may emit the JSON on a single line, across multiple lines, + # or wrapped in a ```json ... ``` code block. Handle all three. result_match = re.search( - r"^IMPLEMENTATION_RESULT:\s*(\{.*\})\s*$", + r"^IMPLEMENTATION_RESULT:\s*(?:```(?:json)?\s*)?(\{.*?\})\s*(?:```\s*)?$", output, - re.MULTILINE, + re.MULTILINE | re.DOTALL, ) if not result_match: raise ValueError(