Hmbown/CodeWhale · error
Review pass / did not return valid structured JSON; the…
Error message
Review pass {expected}/{} did not return valid structured JSON; the partial review was not accepted or posted. What it means
A review pass was accepted in order but its raw output failed to parse as valid structured JSON (ReviewOutput::from_structured_str returned None). The accumulator rejects the whole partial review so nothing incomplete is posted.
Solutions
- Re-run the failing pass and parse again (transient truncation is common)
- Increase the pass output token limit or shrink the pass's diff chunk via smaller max_chars
- Verify the prompt includes the exact structured-JSON schema instructions
- Strip code fences/leading text, or extend from_structured_str to tolerate them
Example fix
// before
let raw = model.complete(pass_prompt).await?;
acc.accept(&pass.manifest, raw)?;
// after
let mut raw = model.complete(pass_prompt).await?;
if !ReviewOutput::parses(&raw) { raw = model.complete(pass_prompt).await?; } // retry once
acc.accept(&pass.manifest, raw)?; Defensive patterns
Strategy: try-catch
Validate before calling
if !ReviewOutput::parses(&raw) { /* retry the pass before accepting */ } Try / catch
match acc.accept(&pass.manifest, raw) { Err(e) if e.to_string().contains("valid structured JSON") => retry_pass(pass).await, ... } Prevention
- Include the exact structured-JSON schema in every pass prompt
- Set output token limits high enough to avoid truncated JSON
- Strip markdown fences before parsing; retry a pass once on parse failure
When it happens
Trigger: Model returned prose or truncated JSON instead of the required structured format for pass N; response cut off by max_tokens; JSON wrapped in markdown fences or with stray text the parser does not strip.
Common situations: Pass prompt too large so the model truncates; model ignoring the output schema on later passes; parser version stricter than what an older model emits.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
- agy OAuth token JSON carries no access token member
- Antigravity cloud-code SSE is not JSON
- Burn rate is optional. When set, it must be a positive $/hr.
- Cannot parse portable data; use JSON for OpenCode or plain…
- Cargo metadata dependencies for
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/d58823e015694443.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tools/review.rs:710
impl PrReviewAccumulator {
pub(crate) fn new(plan: &PrReviewPlan) -> Self {
Self {
manifest: plan.manifest.clone(),
outputs: Vec::new(),
raw_outputs: Vec::new(),
}
}
pub(crate) fn accept(&mut self, pass: &PrReviewPass, raw: String) -> anyhow::Result<()> {
let expected = self.outputs.len() + 1;
anyhow::ensure!(
pass.manifest.number == expected
&& self.manifest.passes.get(expected - 1) == Some(&pass.manifest),
"Review pass arrived out of order or does not match the immutable manifest; expected pass {expected}"
);
let output = ReviewOutput::from_structured_str(&raw).ok_or_else(|| {
anyhow::anyhow!(
"Review pass {expected}/{} did not return valid structured JSON; the partial review was not accepted or posted.",
self.manifest.passes.len()
)
})?;
self.outputs.push(output);
self.raw_outputs.push(raw);
Ok(())
}
pub(crate) fn finish(
self,
complete_diff: &str,
) -> anyhow::Result<(ReviewOutput, String, ReviewReceiptCoverage)> {
anyhow::ensure!(
self.outputs.len() == self.manifest.passes.len(),
"Only {}/{} review passes completed; the partial review was not accepted or posted.",
self.outputs.len(),
self.manifest.passes.len()View on GitHub (pinned to 73e0f67d83)