Repository navigation
extractJson gives up when provider output has an unbalanced "{" before the JSON object #237
Description
Summary
extractJson (src/provider-json.ts) is the fallback parser for codex and pi provider output. It scans from the first { and tracks brace depth. If that brace is never closed, which happens when the model's prose mentions a lone { before the real JSON, the inner loop runs to the end of the text with depth !== 0. The function then sets firstBrace = -1 (provider-json.ts:49-51) and returns null, without trying any later {.
A valid JSON object that follows is ignored. parseCodexJson throws codex provider produced unparseable JSON output, and the pi provider fails the same way.
This case is distinct from #30, which added fence and first-object extraction, and from #71, which asks for raw-output logging. The extraction added for #30 only recovers when the first { is balanced.
Steps to Reproduce
Run from the repository root (v0.8.2, commit 366127f) with npx tsx:
import { extractJson } from "./src/provider-json.ts";
console.log(extractJson('Plan: wrap keys in { braces.\n{"ok":true}'));
console.log(extractJson('Looking at fn() { ... the result:\n{"findings":[]}'));
console.log(extractJson('I will output {draft} then {"ok":1}'));Output:
null
null
{ ok: 1 }
The third case works because {draft} is balanced and the parse failure moves on to the next brace. The first two contain an unclosed { and return null.
Expected Behavior
{ ok: true } and { findings: [] }. An unbalanced brace in the text before the JSON should be skipped, just as an unparseable balanced block already is.
Actual Behavior
null. The review or fix step fails with malformed-output even though the provider returned a valid object.
Possible Solution
- When the scan from a given
{ends unbalanced, retry from the next{after it instead of stopping. - Add
extractJsontests with an unbalanced{in leading prose.
if (depth !== 0) {
// Unbalanced "{" (e.g. in prose): retry from the next brace instead of giving up.
firstBrace = text.indexOf("{", firstBrace + 1);
}With this change, both failing inputs above return the expected objects, 'no json { here' and '{{{{' still return null, and src/provider.test.ts passes (140/140). The loop still terminates, because firstBrace strictly increases.
I'm happy to send a PR.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
clawsweeper commentedon Oct 18, 2026
Codex review: this still needs some work.
Summary
This issue describes a current parser defect, and #238 explicitly closes it but remains unmerged.
Reproducibility: Both reported inputs deterministically reach current main's unmatched-brace termination branch; no runtime reproduction or tests were executed during this read-only review.
Root-cause cluster
Relationship:
canonicalCanonical: #237
Summary: This issue is the concrete unmatched-preamble report, with an open PR explicitly proposing its repair.
Members:
fixed_by_candidate: fix: parse provider JSON after unmatched prose #238 - The PR explicitly closes this issue and adds regression coverage for both reported inputs.Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.
Next step
Review #238 against the reported regressions and existing parsing safeguards, including its newly supplied CLI transcript.
Review details
Best possible solution:
Recover JSON in the shared extractor while preserving balanced-candidate priority, nested-draft exclusion, and downstream schema validation.
Do we have a high-confidence way to reproduce the issue?
Both reported inputs deterministically reach current main's unmatched-brace termination branch; no runtime reproduction or tests were executed during this read-only review.
Is this the best way to solve the issue?
Repairing the shared extractor fits its established tolerant-parsing contract, with regressions needed to protect candidate selection and avoid excessive rescanning.
AGENTS.md: found and applied where relevant.
Codex review notes: model internal, reasoning medium; reviewed against 610a26db8437.
Label changes
Label changes:
No label changes.
Label justifications:
P2: A specific provider-response pattern interrupts review or repair despite usable JSON, with no urgent regression established.impact:other: Shared response extraction rejects usable provider results and fails CLI workflow steps.Evidence reviewed
What I checked:
src/provider-json.ts:49, 610a26db8437)src/providers/pi.ts:113, 610a26db8437)src/provider.test.ts:156, 610a26db8437)src/provider-json.ts:50, 429cc9cbb9fd)src/provider-json.ts:49, b8565de5bb68)How this review workflow works
@clawsweeper re-reviewcomments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.@clawsweeper re-reviewor@clawsweeper re-runon an open PR or issue to request a fresh review only.@clawsweeper reviewto request a fresh review only.@clawsweeper explainto ask for more context, or@clawsweeper stopto stop active automation.Reviewed October 19, 2026, 3:23 PM ET / 19:23 UTC.
7 remaining items
steipete commentedon Oct 19, 2026
Fixed in #238. Both reported inputs returned null on the previous parser and now return the expected objects.
Verified the built CLI through the normal Codex adapter with a deterministic provider executable: the same response containing an unmatched prose brace failed with exit 8 / malformed-output using the old parser, then completed one review with zero findings using the fixed parser. No live provider credentials were involved.
The full validation gate passed on Linux: typecheck, lint, formatting, 970 tests passed (2 skipped), coverage, build, and package smoke. Exact-head GitHub CI also passed, including Windows execution and Node 22/24 runtime checks: https://github.com/openclaw/clawpatch/actions/runs/39314280709.