GFM rendering is broken in messages
Problem
While working on Fix GLQL visualization in Duo chat UI (gitlab#573052) we discovered that GitLab Flavored Markdown (GFM) rendering is broken in Duo UI messages.
The issue seems to be that renderGFM
is called multiple times before a message has finished streaming. This causes some of the GFM components like GLQL to error out because they cannot render incomplete markdown.
I've logged the calls for a single user + agent message:
Root cause
The root cause appears to be that the safeguard preventing DuoChatMessage
& MessageBase
from calling renderGFM
until the message has finished streaming is not working.
Debugging the code I see that this.isChunk
always returns false
because this.message.chunkId = undefined
. Below I've logged out the message
obj contents from a Duo agentic conversation.
Click to show logs
Stream started
{
"status": null,
"correlation_id": null,
"message_type": "agent",
"message_sub_type": null,
"timestamp": "2025-10-14T12:22:07.971538+00:00",
"content": "Here",
"tool_info": null,
"additional_context": null,
"requestId": "154-1",
"role": "assistant"
}
Still streaming…
{
"status": null,
"correlation_id": null,
"message_type": "agent",
"message_sub_type": null,
"timestamp": "2025-10-14T12:22:07.971538+00:00",
"content": "Here are the GLQL queries you requested:\n\n```glql\ndisplay: list\nfields: title\nlimit: 10\nquery: state = opened\n```\n\n```glql",
"tool_info": null,
"additional_context": null,
"requestId": "154-1",
"role": "assistant"
}
Finished streaming
{
"message_type": "agent",
"message_sub_type": null,
"content": "Here are the GLQL queries you requested:\n\n```glql\ndisplay: list\nfields: title\nlimit: 10\nquery: state = opened\n```\n\n```glql\ndisplay: list\nfields: title\nlimit: 25\nquery: state = closed\n```",
"timestamp": "2025-10-14T12:22:08.690550+00:00",
"status": "success",
"correlation_id": null,
"tool_info": null,
"additional_context": null,
"requestId": "154-1",
"role": "assistant"
}
Additionally there's a bug in the code where renderGFM
is called immediately in $nextTick
because it's not passed as a callback and is instead executed immediately.
this.$nextTick(this.renderGFM(this.$refs.content))
Proposed solution
Update the message components:
- Only call
renderGFM
once the message has finished streaming.- The only reliable indicator of this seems to be
"status":"success"
- The only reliable indicator of this seems to be
- Ensure that
renderGFM
is only called once per message.- Updates to the messages array are causing each message to spam
renderGFM
.
- Updates to the messages array are causing each message to spam
- Fix the
$nextTick
call ofrenderGFM
.
Example:
if (this.message.status === 'success' && !this.hasCalledGFM && this.$refs.content) {
this.$nextTick(() => {
this.renderGFM(this.$refs.content);
console.log('hydrateContentWithGFM > $nextTick', this.$refs.content);
});
this.hasCalledGFM = true;
}
Applying this fixes the render GFM issues in GitLab and it reduces the calls to 1 for each user + agent message.
Debugging method
- Have this project and GDK installed
- Follow these instructions to link your local Duo UI with GDK
- Modify Duo UI code
Note that I had a few bugs with the instructions and had to run this to fix it:
# in /gdk/gitlab/
cd ee/frontend_islands/apps/duo_next/
yalc add @gitlab/duo-ui
yarn install --check-files
To push changes:
# in duo-ui/
yarn build && yalc push
# Wait a second and then run this in gdk/gitlab/
gdk stop vite && rm -rf node_modules/.vite tmp/cache && gdk start vite