Skip to content

fix(core): ensure single leading system message in CLI requests (fixes #12963)#12988

Open
not-knope wants to merge 2 commits into
continuedev:mainfrom
not-knope:fix/system-message-must-be-first-after-tool-cancel
Open

fix(core): ensure single leading system message in CLI requests (fixes #12963)#12988
not-knope wants to merge 2 commits into
continuedev:mainfrom
not-knope:fix/system-message-must-be-first-after-tool-cancel

Conversation

@not-knope

Copy link
Copy Markdown

Description

Fixes #12963.

When cancelling/denying a tool call, the follow-up request to the model could fail with:

400 - {'error': {'message': 'System message must be at the beginning', ...}}

Root cause: convertFromUnifiedHistoryWithSystemMessage (in core/util/messageConversion.ts, used by the CLI streaming path in extensions/cli/src/stream/streamChatResponse.ts) injected the system message at index 0, but convertFromUnifiedHistory also emitted any system-role messages that already existed inside the history. Those messages can come from:

  • notifications added via ChatHistoryService.addSystemMessage (diffs, "No changes to display", remote/serve helpers, etc.),
  • auto-compaction notices, and
  • a system message stored at history[0] in loaded/remote sessions.

The result was a request with a system message at a non-zero index, which strict OpenAI-compatible providers (e.g. the reporter's aqueduct proxy) reject with a 400 "System message must be at the beginning". It surfaced most often on the follow-up request that is sent right after a tool call is cancelled.

The fix merges all system content (the injected system message plus any system messages found in the history) into a single system message positioned at index 0, so the outgoing request is always valid. Non-system messages keep their original order, so tool-call/tool-result sequences are untouched.

Checklist

  • I've read the contributing guide
  • The relevant docs, if any, have been updated or created
  • The relevant tests, if any, have been updated or created

Tests

Added describe("convertFromUnifiedHistoryWithSystemMessage", ...) cases in extensions/cli/src/messageConversion.test.ts covering:

  • basic injection of the system message at index 0,
  • a history that already starts with a system message (no duplicate; single leading system message),
  • a cancelled tool call combined with a mid-conversation system notification (the exact reproduction), asserting the only system message is at index 0 and the tool result is preserved,
  • an empty base system message with a stored system message in history.

Note

This PR was developed with AI assistance (Cursor), as reflected in the commit's Co-authored-by trailer. All code was reviewed and verified locally before submission.

When converting unified chat history into OpenAI-compatible messages,
`convertFromUnifiedHistoryWithSystemMessage` injected a system message at
the start but also emitted any system messages that already existed inside
the history (e.g. notifications added via `addSystemMessage`, compaction
notices, or a system message stored at `history[0]` in loaded/remote
sessions). This produced a system message at a non-zero index, which strict
providers reject with a 400 "System message must be at the beginning" error.
This was commonly triggered by the follow-up request sent after cancelling a
tool call.

Merge all system content into a single system message positioned at index 0
so the request is always valid, and add tests covering the tool-cancellation
follow-up scenario.

Closes continuedev#12963

Co-authored-by: Cursor <cursor@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@not-knope
not-knope requested a review from a team as a code owner July 14, 2026 16:53
@not-knope
not-knope requested review from sestinj and removed request for a team July 14, 2026 16:53
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jul 14, 2026
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@not-knope

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Malformed Message After Tool Request Cancellation

1 participant