Hmbown/CodeWhale · error
Compaction coverage floor: a last-round user message was…
Error message
Compaction coverage floor: a last-round user message was dropped; history was not replaced.
What it means
The last-round survival contract validates that a compaction replacement history keeps every message of the most recent conversation round. This fires when any user prompt text from the last round is absent from the replacement, so the rewrite is rejected and history is not replaced. It exists because the round can span two user messages and dropping the latest one silently loses the user's most recent request.
Solutions
- Fix the compaction rewrite so every last-round user message is carried into the replacement history verbatim.
- Re-check the `last_round_start` computation — it must walk past a toolless tail to the previous tool-bearing turn and include ALL user messages in the round, not just the earliest.
- Run the coverage-floor tests (`coverage_floor_rejects_*`) to see which scenario reproduces.
- Ensure summaries are prepended/summarized only for turns strictly before the last round.
Example fix
// before: replacement only summarizes, dropping the latest user turn let replacement = vec![summary_message]; // after: keep the whole last round let replacement = [summary_message, last_round.as_slice()].concat();
Defensive patterns
Strategy: validation
Validate before calling
fn keeps_last_round_users(last_round: &[Message], replacement: &[Message]) -> bool {
last_round.iter().filter_map(user_prompt_text_of)
.all(|t| replacement.iter().any(|m| user_prompt_text_of(m) == Some(t)))
} Try / catch
if let Err(e) = compaction::apply_replacement(&replacement) {
if e.to_string().contains("coverage floor") { /* keep original history */ }
} Prevention
- Only summarize turns strictly before the last round
- Include every user message of the last round (the round can span two)
- Add coverage tests when changing compaction code
- Never let a summary displace the current user turn
When it happens
Trigger: `validate_last_round_coverage` (via `validate_survival_contract` or coverage-floor tests) finds a last-round user message whose text does not appear in the replacement history.
Common situations: A custom or newly-introduced compaction rewrite drops the latest user turn; an off-by-one in `last_round_start` boundary computation; editing compaction code so older turns are summarized but the current user turn is also swallowed.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Compaction coverage floor
- Compaction coverage floor: last-round assistant output was…
- Compaction coverage floor: last-round tool call
- Compaction coverage floor: last-round tool result
- Compaction coverage floor: checkpoint receipt was dropped…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/ccd16249fff6c478.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/compaction/last_round.rs:371
})
}
pub(crate) fn validate_last_round_coverage(
original: &[Message],
replacement: &[Message],
) -> Result<()> {
let last_round = protected_last_round(original);
if last_round.is_empty() {
return Ok(());
}
// Every user turn in the round, not the first one `find_map` happens to
// reach. `last_round_start` walks back past a toolless tail to the previous
// tool-bearing turn, so the round routinely spans two user messages -- and
// checking only the earliest let a rewrite drop the *latest* one, which is
// the turn this whole contract exists to keep.
for text in last_round.iter().copied().filter_map(user_prompt_text_of) {
if !survives(&text, replacement, user_prompt_text_of) {
anyhow::bail!(
"Compaction coverage floor: a last-round user message was dropped; history was not replaced."
);
}
}
for id in last_round.iter().copied().flat_map(tool_result_ids) {
if !replacement
.iter()
.any(|message| has_tool_result_id(message, &id))
{
anyhow::bail!(
"Compaction coverage floor: last-round tool result {id} was dropped; history was not replaced."
);
}
}
// The call, not just its result. Keeping a tool_result whose tool_use was
// summarized away leaves an orphaned result that providers reject outright.
for id in last_round.iter().copied().flat_map(tool_use_ids) {
if !replacementView on GitHub (pinned to 73e0f67d83)