diff --git a/src/org_node_trigger.py b/src/org_node_trigger.py index e8d5d38..769984c 100644 --- a/src/org_node_trigger.py +++ b/src/org_node_trigger.py @@ -149,9 +149,22 @@ def trigger_org_node_structured(org_node, prompt, target_folder_path, output_sch required_keys = list(output_schema.get("required", [])) extracted = extract_json_payload(payload.get("result", ""), required_keys=required_keys) if extracted is None: + # 2026-08-15 (real incident, task b243dfe8): this used to truncate + # to [:2000] chars -- fine for the "process exited nonzero" error + # paths above, where a short excerpt is enough context, but actively + # counterproductive here: the WHOLE POINT of this exception is + # diagnosing why extraction failed (genuine model-side truncation + # vs some other JSON-validity issue), and a truncated preview can + # LOOK like the response was cut off mid-generation when that's + # really just this display limit -- indistinguishable from the + # real thing without the full text, which is exactly what happened + # investigating this live. No length cap: this flows into + # task_execution._mark_blocked's str(exc), stored as a plain TEXT + # execution_event column with no practical size limit, so keeping + # the complete response is the honest, actually-diagnosable choice. raise StructuredOutputExtractionError( "could not extract a payload matching required keys {} from org_node={}'s response: {!r}".format( - required_keys, org_node, payload.get("result", "")[:2000] + required_keys, org_node, payload.get("result", "") ) ) return extracted diff --git a/src/test_org_node_trigger.py b/src/test_org_node_trigger.py index 2ae16e9..feb202a 100644 --- a/src/test_org_node_trigger.py +++ b/src/test_org_node_trigger.py @@ -119,6 +119,25 @@ class TriggerOrgNodeStructuredTest(unittest.TestCase): "validator", "check this", "/tmp/some-project", _SCHEMA ) + def test_extraction_error_preserves_the_full_response_not_truncated(self): + # Real incident (2026-08-15, task b243dfe8): this used to truncate + # the failing response to [:2000] chars in the exception message -- + # diagnosing a real production failure with only that preview, it + # was impossible to tell "the model's response was genuinely cut + # off mid-generation" from "this is just where the 2000-char + # display limit landed" -- indistinguishable without the full text. + long_non_json_response = "not json at all, " * 200 # well over 2000 chars + self.assertGreater(len(long_non_json_response), 2000) + with patch("src.org_node_trigger.subprocess.run") as run: + run.return_value = _completed_process( + stdout_obj={"is_error": False, "result": long_non_json_response} + ) + with self.assertRaises(org_node_trigger.StructuredOutputExtractionError) as ctx: + org_node_trigger.trigger_org_node_structured( + "validator", "check this", "/tmp/some-project", _SCHEMA + ) + self.assertIn(long_non_json_response, str(ctx.exception)) + def test_appends_json_fence_instruction_and_schema_to_prompt(self): with patch("src.org_node_trigger.subprocess.run") as run: run.return_value = _completed_process(