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

  1. Re-run the failing pass and parse again (transient truncation is common)
  2. Increase the pass output token limit or shrink the pass's diff chunk via smaller max_chars
  3. Verify the prompt includes the exact structured-JSON schema instructions
  4. 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

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


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)