Skip to content

[Bug]: DCP_PAIRED_TAG_REGEX matches across injected message ID tags, truncating message content #556

Description

@jwhandy3

Bug Description

stripHallucinations runs at the start of each createChatMessageTransformHandler cycle (line 7444), before injectMessageIds (line 7468). But it operates on ALL messages — including prior turns that already had <dcp-message-id>mXXXX</dcp-message-id> appended by the previous cycle's injectMessageIds.

If a prior assistant message contains <dcp-message-id> anywhere in its text content (e.g., the model mentioned the tag name), DCP_PAIRED_TAG_REGEX treats the injected closing tag at the end of the message as the closing match. Everything between the in-content opening tag and the injected closing tag is deleted.

Reproduced with node:

const DCP_PAIRED_TAG_REGEX = /<dcp[^>]*>[\s\S]*?<\/dcp[^>]*>/gi;
const DCP_UNPAIRED_TAG_REGEX = /<\/?dcp[^>]*>/gi;

const input =
  'The tag called `<dcp-message-id>` is used to track messages. ' +
  'This text should survive.\n\n' +
  '<dcp-message-id>m0369</dcp-message-id>';

const result = input
  .replace(DCP_PAIRED_TAG_REGEX, '')
  .replace(DCP_UNPAIRED_TAG_REGEX, '');

// result: "The tag called `"
// Everything after the in-content tag is gone.

Expected Behavior

Content after an in-text DCP tag mention should not be deleted. Only hallucinated tag pairs that are entirely within the generated content should be stripped.

Debug Context Logs

{
  "role": "assistant",
  "parts": [
    {
      "type": "text",
      "text": "The tag called `"
    }
  ]
}


*(Full content truncated — all text after the tag mention is gone, including the rest of the sentence and subsequent paragraphs)*

Tool Call Details

No response

DCP Version

3.1.12

Opencode Version

1.15.12

Model

Claude Sonnet 4

Additional Context

Add one pre-processing step in stripHallucinationsFromString to remove the legitimately injected message ID suffix before running the paired regex. The injected tag can't then act as a false closing match. injectMessageIds re-adds it later in the same cycle.

// New constant:
const INJECTED_TAG_SUFFIX_REGEX = /\n<dcp-message-id>m\d+<\/dcp-message-id>\s*$/;

// Updated function (one new line, existing replaces unchanged):
var stripHallucinationsFromString = (text) => {
  const safe = text.replace(INJECTED_TAG_SUFFIX_REGEX, "");
  return safe.replace(DCP_PAIRED_TAG_REGEX, "").replace(DCP_UNPAIRED_TAG_REGEX, "");
};

This is additive — the existing two .replace() calls are unchanged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions