Skip to content

Bug: kimi export crashes during context compaction — API 400 text content is empty #2396

@rastinder

Description

@rastinder

Summary

Typing kimi export can fail with a Moonshot API 400 error when the session enters context compaction. The compaction message contains empty/whitespace-only TextParts, which the API rejects.

Environment

  • kimi-cli version: 1.45.0
  • Python version: 3.12.13
  • OS: Linux 6.18.27-amd64-desktop-rolling (x86_64)
  • Model: kimi-code/kimi-for-coding

Steps to Reproduce

  1. Start a session that accumulates enough context to trigger auto-compaction.
  2. Ensure some messages in history have empty or whitespace-only TextParts (e.g., tool results with no text output).
  3. Run kimi export.
  4. The shell begins compaction (CompactionBegin) and immediately crashes.

Actual Behavior

2026-05-28 21:11:28.599 | CompactionBegin
2026-05-28 21:11:30.254 | StepInterrupted
2026-05-28 21:11:30.254 | ERROR | Context compaction failed at step 1: APIStatusError: Error code: 400 - text content is empty

Expected Behavior

kimi export should complete successfully even when the context contains messages with empty text content.

Root Cause

In kimi_cli/soul/compaction.py, SimpleCompaction.prepare() builds a compaction prompt by iterating over historical messages and appending their TextParts:

compact_message.content.extend(
    part for part in msg.content if isinstance(part, TextPart)
)

This does not filter out TextParts where text == "" or text.strip() == "". When the resulting compaction message is sent to kosong.step(), the Moonshot API rejects it with:
{"error": {"message": "text content is empty", "type": "invalid_request_error"}}

A similar bug for tool messages was previously fixed (see #1663 in message.py), but the compaction path was missed.

Proposed Fix

Two defensive changes in kimi_cli/soul/compaction.py, method SimpleCompaction.prepare():

1. Filter empty text parts

compact_message.content.extend(
    part for part in msg.content
    if isinstance(part, TextPart) and part.text.strip()
)

2. Skip compaction if nothing meaningful remains

compact_message.content.append(TextPart(text=prompt_text))
if not any(
    isinstance(p, TextPart) and p.text.strip()
    for p in compact_message.content
):
    return self.PrepareResult(compact_message=None, to_preserve=messages)
return self.PrepareResult(compact_message=compact_message, to_preserve=to_preserve)

Attachments

Sanitized session export (PII redacted): session-df52da1f-sanitized.zip

Related PR

Additional Context

The crash occurs at the kosong.step() call inside SimpleCompaction.compact(), not during serialization of the final compacted result. Therefore the fix must be applied before the LLM call, not after.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions