Skip to content

apply-ai-code parser silently drops a file and corrupts another when a </file> tag is missing #215

Description

@thejesh23

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions