Skip to content

fix(agent-core): drop thinking-only assistant messages from projected requests - #2689

Open
xiahuaaaa wants to merge 1 commit into
MoonshotAI:mainfrom
xiahuaaaa:fix/esc-interrupt-thinking-only-message
Open

fix(agent-core): drop thinking-only assistant messages from projected requests#2689
xiahuaaaa wants to merge 1 commit into
MoonshotAI:mainfrom
xiahuaaaa:fix/esc-interrupt-thinking-only-message

Conversation

@xiahuaaaa

@xiahuaaaa xiahuaaaa commented Aug 6, 2026

Copy link
Copy Markdown

Related Issue

Resolve #2691 — reproducible ESC-interrupt crash (bug report).

Problem

Reproducible session crash: pressing ESC to interrupt an ongoing response wedges the session permanently. When the stream is cancelled mid-thinking (the model's first output chunk is usually a thinking part), the interrupted step leaves a partial assistant message in the conversation history that holds only an unencrypted thinking fragment — no text, no tool calls.

On the next user prompt, that message is sealed and projected into the request. Every protocol base moves thinking out of content:

  • OpenAI bases: convertContentPart returns null for think parts; the reasoning is moved to reasoning_content.
  • Anthropic base: think parts become thinking blocks.

So the sealed message serializes with neither content nor tool_calls, and the provider rejects it:

400 Invalid assistant message: content or tool_calls must be set

Every subsequent request fails the same way (the message stays in history), so the session cannot recover — the TUI shows the error and each retry re-fails. Reproduced with a third-party OpenAI-compatible provider (deepseek) on the v2 engine; the v1 engine has the identical drop/keep logic and the same failure mode.

What changed

The projector already drops "wholly-vacuous" assistant messages (nothing the provider wire can represent) — e.g. a message whose only part is an empty thinking block. The gap was that a non-empty unencrypted thinking block was treated as wire-sendable content, when it serializes to nothing on every protocol.

Both engines now define what is actually sendable on the provider wire — everything except unencrypted thinking blocks — and drop assistant messages whose only parts are unencrypted thinking:

  • packages/agent-core/src/agent/context/projector.ts (v1 engine)
  • packages/agent-core-v2/src/agent/contextProjector/contextProjectorService.ts (v2 engine)

The context history still keeps the interrupted reasoning (visible in the transcript, preserved on resume); only the outgoing request drops it, and a vacuous_message_dropped projection repair is reported as before. Signed thinking (encrypted) is deliberately preserved — reasoning providers require it echoed back verbatim.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 115114adbc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +439 to +443
// Unencrypted thinking blocks carry nothing the provider wire can
// represent as message content: the OpenAI bases drop them out of
// `content` (`convertContentPart` → `null`, moved to `reasoning_content`)
// and the Anthropic base moves them to `thinking` blocks. A message left
// with only unencrypted thinking would serialize with neither content nor

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move the new comments to the file header

The newly added implementation narration here, together with the helper-level block above wireSendableContent, violates the package's header-only comment convention. Consolidate the externally relevant responsibility into the top-of-file header and remove the comments beside statements/functions so this v2 module remains consistent with its documented structure.

AGENTS.md reference: packages/agent-core-v2/AGENTS.md:L15-L19

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — moved the rationale into the top-of-file header and removed the inline block in emit plus the helper-level JSDoc, so the v2 module keeps its header-only comment convention.

… requests

Interrupting a response mid-thinking (ESC) seals a partial assistant
message holding only an unencrypted thinking fragment. Every protocol
base moves thinking out of `content` (OpenAI `convertContentPart` returns
null, Anthropic maps it to `thinking` blocks), so the next request
serialized that message with neither content nor tool_calls and was
rejected by the provider (400 "Invalid assistant message: content or
tool_calls must be set"), permanently wedging the session.

Treat unencrypted thinking as not wire-sendable in the projector's
vacuous-message drop check: such messages are now dropped from outgoing
requests while the context keeps the reasoning for display and resume.
Signed thinking (`encrypted`) is preserved for reasoning providers that
require the echo back verbatim.
@xiahuaaaa
xiahuaaaa force-pushed the fix/esc-interrupt-thinking-only-message branch from 115114a to db67e3a Compare August 6, 2026 08:13
@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: db67e3a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pressing ESC mid-stream permanently wedges the session: next request fails with 400 Invalid assistant message: content or tool_calls must be set

1 participant