Hmbown/CodeWhale · error

Compaction coverage floor: last-round tool call

Error message

Compaction coverage floor: last-round tool call {id} was dropped; history was not replaced.

What it means

The survival contract requires every tool call (tool_use) from the last round to survive compaction. Keeping a tool_result while summarizing away its tool_use leaves an orphaned result that providers reject outright, so a replacement dropping a tool_use id is rejected before it can replace history.

Solutions

  1. Carry the last-round tool_use blocks (and their assistant message) into the replacement unmodified.
  2. Ensure tool_use and its tool_result are kept or dropped as a pair — never one alone.
  3. Fix summarization boundaries so only pre-last-round turns are rewritten.
  4. Check `tool_use_ids` extraction covers all last-round messages.

Example fix

// before: orphaned result
replacement = [summary, tool_result_msg];
// after: keep call+result pair
replacement = [summary, tool_use_msg, tool_result_msg];
Defensive patterns

Strategy: validation

Validate before calling

fn keeps_tool_uses(last_round: &[Message], replacement: &[Message]) -> bool {
    last_round.iter().flat_map(tool_use_ids)
        .all(|id| replacement.iter().any(|m| has_tool_use_id(m, &id)))
}

Try / catch

match compaction::apply_replacement(&replacement) {
    Err(e) if e.to_string().contains("tool call") => keep_original_history(),
    other => other,
}

Prevention

When it happens

Trigger: `validate_last_round_coverage` iterates last-round `tool_use_ids` and finds no message in the replacement carrying that tool_use id.

Common situations: A rewrite keeps tool results but summarizes the assistant message containing the tool calls; a partial rewrite drops the assistant tool_use block; provider-strict pairing means this must fail fast.

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


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/1d1e78486953450e. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/compaction/last_round.rs:393

    }
    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 !replacement
            .iter()
            .any(|message| has_tool_use_id(message, &id))
        {
            anyhow::bail!(
                "Compaction coverage floor: last-round tool call {id} was dropped; history was not replaced."
            );
        }
    }
    // Match the assistant's actual output. An existential "some assistant
    // message survived" check passed on a replacement whose only assistant
    // message was the summary the rewrite had just written.
    for text in last_round.iter().copied().filter_map(assistant_text_of) {
        if !survives(&text, replacement, assistant_text_of) {
            anyhow::bail!(
                "Compaction coverage floor: last-round assistant output was dropped; history was not replaced."
            );
        }
    }
    if last_round
        .iter()
        .any(|message| message.role.is_assistant_like())
        && !replacement

View on GitHub (pinned to 73e0f67d83)