{"record":{"id":"94e20b960d4ba153","repo":"nikivdev/code","slug":"openrouter-returned-empty-review-output","errorCode":null,"errorMessage":"OpenRouter returned empty review output","messagePattern":"OpenRouter returned empty review output","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/commit.rs","lineNumber":6217,"sourceCode":"    let start = std::time::Instant::now();\n\n    let parsed: ChatResponse = openrouter_chat_completion_with_retry(&client, &api_key, &body)\n        .context(\"OpenRouter request failed\")?;\n\n    info!(\n        elapsed_ms = start.elapsed().as_millis() as u64,\n        \"OpenRouter responded\"\n    );\n\n    let output = parsed\n        .choices\n        .first()\n        .and_then(|c| c.message.as_ref())\n        .map(|m| m.content.trim().to_string())\n        .unwrap_or_default();\n\n    if output.is_empty() {\n        bail!(\"OpenRouter returned empty review output\");\n    }\n\n    println!(\"{}\", output);\n\n    let mut review_json = parse_review_json(&output);\n    let future_tasks = review_json\n        .as_ref()\n        .map(|json| normalize_future_tasks(&json.future_tasks))\n        .unwrap_or_default();\n    let mut summary = review_json.as_ref().and_then(|r| r.summary.clone());\n    let quality = review_json.as_mut().and_then(|r| r.quality.take());\n    let (mut issues_found, mut issues) = if let Some(ref json) = review_json {\n        (json.issues_found, json.issues.clone())\n    } else {\n        let lowered = output.to_lowercase();\n        let has_issues = lowered.contains(\"bug\")\n            || lowered.contains(\"issue\")\n            || lowered.contains(\"error\")","sourceCodeStart":6199,"sourceCodeEnd":6235,"githubUrl":"https://github.com/nikivdev/code/blob/a747e741ae92c09071d0ae946ab48488adcff1ce/src/commit.rs#L6199-L6235","documentation":"The AI code-review feature calls OpenRouter's chat-completions API and expects a non-empty assistant message. After the HTTP request succeeds, the tool extracts choices[0].message.content, trims it, and throws this error if the result is an empty string. It means OpenRouter returned a 200 response whose payload contained no usable text.","triggerScenarios":"Calling the commit review flow when OpenRouter returns a response with an empty choices array, a choice with no message object, or message.content that is whitespace-only (e.g. model produced a refusal or empty completion).","commonSituations":"Weak or degraded model selected (empty completions), prompt too large so the model returns nothing, provider-side moderation blocking output, malformed/missing API response fields, or a model that outputs only tool-call/refusal content.","solutions":["Re-run the review; transient empty completions often succeed on retry","Switch to a different/more capable model via the model config","Verify the OpenRouter API key and account quota at openrouter.ai","Log the raw response body to confirm whether choices/content are missing","Reduce the diff size sent in the prompt so the model can produce output"],"exampleFix":"// before\nlet output = parsed.choices.first()\n    .and_then(|c| c.message.as_ref())\n    .map(|m| m.content.trim().to_string())\n    .unwrap_or_default();\nif output.is_empty() { bail!(\"OpenRouter returned empty review output\"); }\n// after\nlet output = parsed.choices.first()\n    .and_then(|c| c.message.as_ref())\n    .map(|m| m.content.trim().to_string())\n    .unwrap_or_default();\nlet output = if output.is_empty() {\n    eprintln!(\"empty review output; retrying once\");\n    retry_request(&client, &api_key, &body)?\n} else { output };","handlingStrategy":"retry","validationCode":"let body = serde_json::to_string(&request)?;\n// after the response:\nlet text = parsed.choices.first()\n    .and_then(|c| c.message.as_ref())\n    .map(|m| m.content.trim().to_string())\n    .unwrap_or_default();\nif text.is_empty() { eprintln!(\"warning: empty model output; will retry\"); }","typeGuard":"fn non_empty_review(resp: &ChatResponse) -> Option<&str> {\n    resp.choices.first()\n        .and_then(|c| c.message.as_ref())\n        .map(|m| m.content.trim())\n        .filter(|s| !s.is_empty())\n}","tryCatchPattern":"match run_review() {\n    Err(e) if e.to_string().contains(\"empty review output\") => {\n        eprintln!(\"model returned nothing; retrying with different model\");\n        run_review_with_fallback_model()?;\n    }\n    Err(e) => return Err(e),\n    Ok(v) => v,\n}","preventionTips":["Pick a reliable review model with non-trivial max output tokens","Keep prompts/diffs under model context limits","Log raw OpenRouter response bodies for diagnosis","Implement one automatic retry on empty output","Monitor OpenRouter provider status/incidents"],"tags":["http","ai","openrouter","empty-response"],"backgroundTag":"llm-empty-response","analyzedSha":"a747e741ae92c09071d0ae946ab48488adcff1ce","analyzedAt":"2026-09-01T22:43:55.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}