{"record":{"id":"cee0ae934da6baf5","repo":"Hmbown/CodeWhale","slug":"compaction-summary-response-was-unusable-no-text","errorCode":null,"errorMessage":"Compaction summary response was unusable: no text was returned.","messagePattern":"Compaction summary response was unusable: no text was returned\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/compaction.rs","lineNumber":1125,"sourceCode":"    let mut prompt = format!(\n        \"The previous handoff response was empty or a placeholder. Return a substantive factual \\\ncontinuation handoff. State the user objective, completed and current work, hard constraints, verified \\\nevidence, unresolved failures, and the single next action. Do not refuse, call tools, discuss \\\ncheckpoint machinery, or return a placeholder. {COMPACTION_LANGUAGE_CONTRACT}\"\n    );\n    if let Some(focus) = focus.map(str::trim).filter(|focus| !focus.is_empty()) {\n        let _ = write!(\n            prompt,\n            \"\\n\\nThe user asked this compaction to focus on: {focus}\"\n        );\n    }\n    prompt\n}\n\nfn validate_compaction_summary(summary: &str) -> Result<()> {\n    let trimmed = summary.trim();\n    if trimmed.is_empty() {\n        anyhow::bail!(\"Compaction summary response was unusable: no text was returned.\");\n    }\n\n    // Strip every non-word edge, not just ASCII punctuation. Providers can\n    // return visually non-empty Unicode punctuation or emoji-only payloads;\n    // neither is a usable continuation checkpoint. `is_alphanumeric` keeps\n    // this language-neutral for CJK and other scripts without imposing a\n    // prose-length heuristic.\n    let normalized = trimmed\n        .trim_matches(|ch: char| !ch.is_alphanumeric())\n        .to_ascii_lowercase();\n    if normalized.is_empty() {\n        anyhow::bail!(\n            \"Compaction summary response was unusable: only whitespace or punctuation was returned.\"\n        );\n    }\n    if matches!(\n        normalized.as_str(),\n        \"no summary available\"","sourceCodeStart":1107,"sourceCodeEnd":1143,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/compaction.rs#L1107-L1143","documentation":"validate_compaction_summary (crates/tui/src/compaction.rs:1125) rejects a summary that is empty after trim. The compaction path prefers failing - and keeping the full history - over replacing the session with an empty continuation checkpoint.","triggerScenarios":"The summary model returns an empty assistant message (tool-call-only completion, response_format forcing empty output) or a gateway strips the content block.","commonSituations":"Small models with weak instruction-following asked to summarize very long histories; providers under content filtering that blank responses; misconfigured gateways.","solutions":["Retry compaction - a single empty response is usually stochastic","Use a model with stronger instruction-following for the summary step","Check the provider/gateway for content filtering or blocklists that empty responses","Confirm the compaction model actually supports plain text output for the configured response format"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"fn is_unusable_summary(msg: &str) -> bool {\n    msg.contains(\"Compaction summary response was unusable\")\n}","tryCatchPattern":"// Retry once with a different model, then surface: never persist an empty checkpoint\nmatch compact().await {\n    Err(e) if is_unusable_summary(&e.to_string()) => compact_with_model(fallback_model).await,\n    r => r,\n}","preventionTips":["Use instruction-following models for summarization","Watch for content filters that blank responses","Rely on the failure: history is preserved, so retrying is always safe"],"tags":["compaction","validation","empty-response","provider"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}