StreamedChatMessageIterator buffer corrupts HTML when consuming streamed responses server-side (relative URLs split mid-attribute)
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3579967. --> Reported by: [increweb21](https://www.drupal.org/user/3651601) Related to !1328 >>> <p>[Tracker]<br> <strong>Update Summary: </strong>Buffer flushes mid-attribute on relative URLs, corrupting HTML in server-side consumers<br> <strong>Short Description: </strong>StreamedChatMessageIterator buffer corrupts HTML when consuming streamed responses server-side (relative URLs split mid-attribute)<br> <strong>Check-in Date: </strong>03/18/2026<br> <em>Metadata is used by the <a href="https://www.drupalstarforge.ai/" title="AI Tracker">AI Tracker.</a> Docs and additional fields <a href="https://www.drupalstarforge.ai/ai-dashboard/docs" title="AI Issue Tracker Documentation">here</a>.</em><br> [/Tracker]</p> <h3 id="summary-problem-motivation">Problem/Motivation</h3> <p>When using the AI module's Chat operation type to process HTML-rich content server-side (e.g. via <code>ai_integration_eca</code> or any server-side batch consumer), the output HTML is consistently corrupted. Attributes are split mid-value, tags are broken, table structures are destroyed, and <code>src</code>/<code>href</code> URLs are truncated.</p> <p>The root cause is in <code>StreamedChatMessageIterator</code>, where the buffer flushes when <code>$maxBufferSize</code> (100 characters) is reached or on every newline. This causes the buffer to flush mid-attribute when processing HTML content with relative URLs like <code>src="/sites/default/files/..."</code>.</p> <p><strong>Note:</strong> as of Drupal 11.3, Fibers are supported and any AI provider call runs in streaming mode &mdash; including server-side consumers like <code>ai_integration_eca</code>. There is no way to avoid this code path.</p> <h4 id="summary-steps-reproduce">Steps to reproduce (required for bugs, not feature requests)</h4> <ol> <li>Install Drupal 11.3+ with the AI module and <code>ai_integration_eca</code></li> <li>Configure an ECA Chat action that processes a node body field containing images with relative URLs (<code>src="/sites/default/files/..."</code>), HTML tables, and links</li> <li>Trigger the ECA model</li> <li>Observe the output &mdash; HTML attributes are split, tags are broken, table structure is destroyed</li> </ol> <h4>Example of corruption observed</h4> <p>Debugging with Xdebug inside <code>flushInternal()</code>, two consecutive flushes were observed:</p> <p>Flush 1 (buffer limit reached mid-attribute):<br> <code>src="/sites/default"</code></p> <p>Flush 2 (rest of the URL):<br> <code>/files/inline-images/image.png" width="2816" height="1536"&gt;</code></p> <p>Resulting in broken HTML with the rest of the URL orphaned outside the tag.</p> <h3 id="summary-proposed-resolution">Proposed resolution</h3> <ol> <li>Fix the regex in <code>shouldFlush()</code> to protect relative URLs starting with <code>/</code>:</li> </ol> <pre>// Before &mdash; only protects absolute URLs<br>if (preg_match('/"(?:http|\/\/:)[^"]*$/', $this-&gt;buffer)...<br><br>// After &mdash; also protects relative URLs<br>if (preg_match('/"(?:http|\/)[^"]*$/', $this-&gt;buffer)...</pre><ol start="2"> <li>Consider increasing <code>$maxBufferSize</code> or making it configurable to reduce the risk of mid-attribute flushes on long attribute values.</li> </ol> <h3 id="summary-remaining-tasks">Remaining tasks</h3> <ul> <li>Fix the regex in <code>shouldFlush()</code> to protect relative URLs</li> <li>Add kernel test that verifies HTML with relative URLs is not corrupted when consuming the iterator server-side</li> </ul> <h3 id="summary-user-interface-changes">User interface changes</h3> <p>None.</p> <h3 id="summary-api-changes">API changes</h3> <p>None.</p> <h3 id="summary-data-model-changes">Data model changes</h3> <p>None.</p> <h3 id="summary-release-notes-snippet">Release notes snippet</h3> <p>Fixed HTML corruption in <code>StreamedChatMessageIterator</code> when processing content with relative URLs server-side (e.g. via ECA or batch processing).</p>
issue