Skip to content

filterCompressedRanges() can leave message array ending with assistant message, causing Anthropic prefill error #470

@marciocloudflare

Description

@marciocloudflare

Description

After installing DCP, I'm frequently getting this error from the Anthropic API:

"This model does not support assistant message prefill. The conversation must end with a user message."

The error requires manually resuming the session each time. It started happening significantly more often after installing DCP and correlates with DCP's message manipulation.

Root Cause Analysis

The following analysis was performed by Claude Opus 4.6 by inspecting DCP's distributed source code.

filterCompressedRanges() in prune.js rebuilds the message array by dropping pruned messages and injecting synthetic user summaries at anchor points. However, there is no validation that the resulting array ends with a user or tool message.

If a compression block covers the tail of the conversation — or if the last surviving message after pruning happens to be an assistant message — the array is sent to the Anthropic API as-is, which rejects it because it ends with an assistant message.

This is not caught anywhere in the 16-step pipeline in createChatMessageTransformHandler (hooks.js). There is no post-pipeline tail-role assertion.

Secondary risk areas:

  • applyPendingManualTrigger() silently does nothing if it can't find a user message to replace
  • injectCompressNudges() happily injects into a trailing assistant message rather than flagging the structural problem

Steps to Reproduce

  1. Install DCP with default or custom config
  2. Use anthropic/claude-opus-4-6 (or any Anthropic model)
  3. Run a long session where DCP compresses content near the tail of the conversation
  4. Observe the prefill error when the next LLM request fires

The error is intermittent and depends on which messages get pruned and where the compression anchor lands relative to the conversation tail.

Suggested Fix

Add a tail-role guard at the end of createChatMessageTransformHandler in hooks.js, after all pipeline steps complete (after line ~64, before logger.saveContext()):

// Ensure the message array doesn't end with an assistant message
if (output.messages.length > 0) {
  const lastMsg = output.messages[output.messages.length - 1];
  if (lastMsg.info.role === "assistant") {
    // Append a synthetic user message to satisfy API requirements
    output.messages.push(createSyntheticUserMessage(/* ... */));
  }
}

The createSyntheticUserMessage helper already exists in messages/utils.js and could be reused here.

Environment

  • OpenCode: latest
  • DCP: @tarquinen/opencode-dcp@latest (3.1.5)
  • Model: anthropic/claude-opus-4-6
  • OS: macOS

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions