Skip to content

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

@jwhandy3

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
    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