Hmbown/CodeWhale · error

Compaction coverage floor: last-round tool result

Error message

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

What it means

The survival contract requires every tool result from the last round to survive compaction. If a replacement history drops a tool result with a given id, the rewrite is rejected. Dropping tool results breaks the tool_use/tool_result pairing providers require.

Solutions

  1. Preserve the last-round tool results verbatim in the replacement history.
  2. Fix the rewrite so summarization only applies to rounds before the last one.
  3. Verify `tool_result_ids` extraction covers all messages of the last round (a missed message hides ids from validation).
  4. Run the coverage-floor tests to identify the exact drop.

Example fix

// before: tool results summarized away
let replacement: Vec<Message> = summarize(last_round);
// after: copy tool-result blocks through untouched
let replacement: Vec<Message> = keep_tool_blocks(last_round);
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

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

Prevention

When it happens

Trigger: `validate_last_round_coverage` iterates last-round `tool_result_ids` and finds no message in the replacement containing that tool result id.

Common situations: A rewrite summarizes the last round's tool exchange into prose; a filtering bug drops tool-result blocks; newly added compaction logic prunes 'large' tool outputs including last-round ones.

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/29ed6597c4edfa23. Report an issue: GitHub.

Appendix: source

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

    }
    // 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 !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

View on GitHub (pinned to 73e0f67d83)