Summary
The file-block parser in apply-ai-code silently drops a file and corrupts another when the model's output is missing a </file> closing tag on an earlier <file> block. The dropped file is never written, and the corrupted file is marked as "complete" so no truncation warning is emitted.
Location
app/api/apply-ai-code/route.ts:32 (parseAIResponse)
app/api/apply-ai-code-stream/route.ts:70 (same regex)
app/api/generate-ai-code-stream/route.ts:1568 (strict variant of the same parser)
const fileRegex = /<file path="([^"]+)">([\s\S]*?)(?:<\/file>|$)/g;
Root cause
The lazy [\s\S]*? stops at the first place the trailing group can match. If an earlier <file> block is missing its </file>, the only </file> (or end-of-string) is after a later file, so the first capture spans across the next file's opening tag — absorbing that later file's tag + body and never emitting it as its own entry. There is no (?=<file path=") boundary between blocks.
Worse, in apply-ai-code/route.ts the merged match ends with </file>, so hasClosingTag (line 37) is true and the block is treated as complete — no "appears to be truncated" warning fires, and the second file is silently lost.
Reproduction
Input (file A is missing its </file>):
<file path="src/A.jsx">AAA<file path="src/B.jsx">BBB</file>
Current parse result:
[{ "path": "src/A.jsx", "content": "AAA<file path=\"src/B.jsx\">BBB", "hasClosingTag": true }]
src/B.jsx is dropped entirely, and src/A.jsx is written with the next file's tag embedded in its body — and flagged complete, so no warning.
Impact
A single malformed/truncated </file> (common with LLM output) silently discards a whole file the user asked for and writes garbage into another, with no diagnostic. The user sees a broken app and no indication a file went missing.
Suggested fix
Add a (?=<file path=") look-ahead as an additional block terminator so a block always ends at the next file's opening tag:
-const fileRegex = /<file path="([^"]+)">([\s\S]*?)(?:<\/file>|$)/g;
+const fileRegex = /<file path="([^"]+)">([\s\S]*?)(?:<\/file>|(?=<file path=")|$)/g;
With the fix the reproduction above parses to [("src/A.jsx","AAA"), ("src/B.jsx","BBB")], A is correctly flagged as missing its closing tag, and well-formed input / a truncated final file still parse exactly as before.
I have a fix ready and will open a PR referencing this issue.
Summary
The file-block parser in
apply-ai-codesilently drops a file and corrupts another when the model's output is missing a</file>closing tag on an earlier<file>block. The dropped file is never written, and the corrupted file is marked as "complete" so no truncation warning is emitted.Location
app/api/apply-ai-code/route.ts:32(parseAIResponse)app/api/apply-ai-code-stream/route.ts:70(same regex)app/api/generate-ai-code-stream/route.ts:1568(strict variant of the same parser)Root cause
The lazy
[\s\S]*?stops at the first place the trailing group can match. If an earlier<file>block is missing its</file>, the only</file>(or end-of-string) is after a later file, so the first capture spans across the next file's opening tag — absorbing that later file's tag + body and never emitting it as its own entry. There is no(?=<file path=")boundary between blocks.Worse, in
apply-ai-code/route.tsthe merged match ends with</file>, sohasClosingTag(line 37) istrueand the block is treated as complete — no "appears to be truncated" warning fires, and the second file is silently lost.Reproduction
Input (file
Ais missing its</file>):Current parse result:
[{ "path": "src/A.jsx", "content": "AAA<file path=\"src/B.jsx\">BBB", "hasClosingTag": true }]src/B.jsxis dropped entirely, andsrc/A.jsxis written with the next file's tag embedded in its body — and flagged complete, so no warning.Impact
A single malformed/truncated
</file>(common with LLM output) silently discards a whole file the user asked for and writes garbage into another, with no diagnostic. The user sees a broken app and no indication a file went missing.Suggested fix
Add a
(?=<file path=")look-ahead as an additional block terminator so a block always ends at the next file's opening tag:With the fix the reproduction above parses to
[("src/A.jsx","AAA"), ("src/B.jsx","BBB")],Ais correctly flagged as missing its closing tag, and well-formed input / a truncated final file still parse exactly as before.I have a fix ready and will open a PR referencing this issue.