Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion python/lean_pool/codex_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import json
import os
import re
import signal
import subprocess
import sys
Expand Down Expand Up @@ -195,7 +196,7 @@ def run_codex(request: dict, directory: Path) -> dict:
if process.returncode:
raise RuntimeError(f"Codex exited {process.returncode}: {stderr[-2000:]}")
envelope = json.loads((directory / "answer.json").read_text())
payload = json.loads(envelope["review_json"])
payload = decode_review_json(envelope["review_json"])
if not isinstance(payload, dict) or not payload:
raise ValueError("Codex did not return a review object")
usage = extract_usage(stdout)
Expand All @@ -207,6 +208,22 @@ def run_codex(request: dict, directory: Path) -> dict:
}


def decode_review_json(source: str) -> Any:
"""Preserve literal Lean/LaTeX backslashes in otherwise valid model JSON."""
try:
return json.loads(source)
except json.JSONDecodeError as error:
if error.msg != "Invalid \\escape":
raise
# Consume valid escape pairs intact, including already escaped backslashes.
Comment on lines +213 to +218

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Common LaTeX escapes still break

When review evidence contains common literal LaTeX commands, this decoder can still corrupt or reject it. Commands such as \frac, \neq, \rightarrow, and \times begin with valid JSON escape letters, so the first json.loads succeeds and converts them into control characters before the repair runs. Commands such as \underbrace and \uparrow instead produce an Invalid \uXXXX escape, which this handler does not repair. As a result, ordinary mathematical evidence can still be changed or cause the full-source review to fail. The related tests cover \lambda and \set, but not these cases.

repaired = re.sub(
r'\\(?:["\\/bfnrtu]|([^"\\/bfnrtu]))',
lambda match: "\\\\" + match[1] if match[1] else match[0],
source,
)
return json.loads(repaired)


def stop_codex(process: subprocess.Popen) -> None:
"""Let the account dispatcher forward termination to its separate child group."""
try:
Expand Down
28 changes: 28 additions & 0 deletions python/tests/test_codex_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,31 @@ def test_azure_partial_usage_labels_partial_estimate():
)
assert "**Estimated cost:** $1.0500" in footer
assert "partial" in footer


def test_review_json_preserves_literal_mathematical_backslashes():
"""An invalid JSON escape must not lose completed mathematical evidence."""
raw = (
r'{"verdict":"discuss","evidence":"\lambda x => x; '
r'\\proper; \nline; \"quote\"; \set"}'
)
result = codex_review.decode_review_json(raw)
assert result == {
"verdict": "discuss",
"evidence": '\\lambda x => x; \\proper; \nline; "quote"; \\set',
}


@pytest.mark.parametrize(
"source", [r'{"evidence":"\u123"}', '{"verdict":"pass"', r'{"x":\broken}']
)
def test_review_json_other_malformed_output_still_fails(source):
"""Backslash handling cannot invent missing structure or repair other errors."""
with pytest.raises(json.JSONDecodeError):
codex_review.decode_review_json(source)


def test_valid_review_json_is_unchanged():
"""Valid escapes and Unicode keep their ordinary JSON meanings."""
payload = {"verdict": "pass", "evidence": '\\lambda\n"quoted"\tλ'}
assert codex_review.decode_review_json(json.dumps(payload)) == payload
Loading