[Bug] LLMs editing python files not handling the newline character. Inserting \n instead of new line #2342

Open
opened 2026-02-16 17:35:13 -05:00 by yindo · 4 comments
Owner

Originally created by @rrmistry on GitHub (Oct 29, 2025).

Description

Bringing this over from Discord chat

Python files were being edited with newlines not being escaped properly.

The files had \n character instead of actual new lines.

The fix was to use the below config:

{
    "formatter": {
        "ruff": {
            "disabled": true
        },
        "black": {
            "command": [
                ".venv/bin/black",
                "--quiet",
                "$FILE"
            ],
            "extensions": [
                ".py",
                ".pyi"
            ]
        }
    }
}

The biggest issue is that this is intermittent and not reliably reproducible

OpenCode version

0.15.18

Steps to reproduce

  1. Setup OpenRouter provider with x-ai/grok-code-fast-1 or openai/gpt-4o-mini
  2. Ask LLM to make multiple edits to the same Python file
  3. Observe the file changes afterwards

Screenshot and/or share link

No response

Operating System

macos

Terminal

default MacOS Terminal app

Originally created by @rrmistry on GitHub (Oct 29, 2025). ### Description Bringing this over from [Discord chat](https://discord.com/channels/1391832426048651334/1432112035633827971) Python files were being edited with newlines not being escaped properly. The files had `\n` character instead of actual new lines. The fix was to use the below config: ```json { "formatter": { "ruff": { "disabled": true }, "black": { "command": [ ".venv/bin/black", "--quiet", "$FILE" ], "extensions": [ ".py", ".pyi" ] } } } ``` The biggest issue is that this is intermittent and not reliably reproducible ### OpenCode version 0.15.18 ### Steps to reproduce 1. Setup OpenRouter provider with `x-ai/grok-code-fast-1` or `openai/gpt-4o-mini` 2. Ask LLM to make multiple edits to the same Python file 3. Observe the file changes afterwards ### Screenshot and/or share link _No response_ ### Operating System macos ### Terminal default MacOS Terminal app
yindo added the help-wantedbug labels 2026-02-16 17:35:13 -05:00
Author
Owner

@github-actions[bot] commented on GitHub (Oct 29, 2025):

This issue might be a duplicate of existing issues. Please check:

  • #2904: Icons/Unicode characters cause file editing failures - This issue describes similar problems with character handling during file editing operations, where Unicode characters and special characters cause edit failures
  • #1676: bug: removed last line break in multi-line replacement - This issue specifically deals with newline handling problems in the file editing system where line breaks are incorrectly processed during replacements
  • #1506: Using file: to import data for sometimes (always?) adds a newline - This issue describes problems with newline characters being incorrectly added when processing file contents
  • #616: opencode keeps trying to format using tools that aren't installed - While not exactly the same, this issue deals with formatter-related problems that could be related to your ruff/black configuration issues

Feel free to ignore if none of these address your specific case.

@github-actions[bot] commented on GitHub (Oct 29, 2025): This issue might be a duplicate of existing issues. Please check: - #2904: Icons/Unicode characters cause file editing failures - This issue describes similar problems with character handling during file editing operations, where Unicode characters and special characters cause edit failures - #1676: bug: removed last line break in multi-line replacement - This issue specifically deals with newline handling problems in the file editing system where line breaks are incorrectly processed during replacements - #1506: Using file: to import data for sometimes (always?) adds a newline - This issue describes problems with newline characters being incorrectly added when processing file contents - #616: opencode keeps trying to format using tools that aren't installed - While not exactly the same, this issue deals with formatter-related problems that could be related to your ruff/black configuration issues Feel free to ignore if none of these address your specific case.
Author
Owner

@rekram1-node commented on GitHub (Oct 29, 2025):

thanks @rrmistry!

Yeah not sure what is causing it we need to look into it more...

@rekram1-node commented on GitHub (Oct 29, 2025): thanks @rrmistry! Yeah not sure what is causing it we need to look into it more...
Author
Owner

@popododo0720 commented on GitHub (Jan 21, 2026):

Hi, I investigated this issue and found the root cause.

Root Cause

The EscapeNormalizedReplacer in edit.ts applies unescapeString() only to oldString (the text to find), but not to newString (the replacement text).

When certain LLMs (like grok-code-fast-1, gpt-4o-mini via OpenRouter) send tool call arguments with double-escaped newlines:

{"newString": "print(\"hello\\nworld\")"}

After JSON parsing, newString becomes "print(\"hello\nworld\")" where \n is a literal backslash + n (2 characters), not an actual newline.

Since newString is written to the file without unescape processing, the literal \n characters end up in the file instead of actual newlines.

Proposed Fix

Extract unescapeString() from EscapeNormalizedReplacer and apply it to newString as well:

// edit.ts line 76
// Before:
contentNew = replace(contentOld, params.oldString, params.newString, params.replaceAll)

// After:
const unescapedNewString = unescapeString(params.newString)
contentNew = replace(contentOld, params.oldString, unescapedNewString, params.replaceAll)

Considerations

  • Need to test edge cases where literal \\n is intended (e.g., regex patterns)
  • Should add test cases for this scenario

Would this approach be acceptable? I would like to work on this if it is still available.

@popododo0720 commented on GitHub (Jan 21, 2026): Hi, I investigated this issue and found the root cause. ## Root Cause The `EscapeNormalizedReplacer` in `edit.ts` applies `unescapeString()` only to `oldString` (the text to find), but **not to `newString`** (the replacement text). When certain LLMs (like grok-code-fast-1, gpt-4o-mini via OpenRouter) send tool call arguments with double-escaped newlines: ``` {"newString": "print(\"hello\\nworld\")"} ``` After JSON parsing, `newString` becomes `"print(\"hello\nworld\")"` where `\n` is a **literal backslash + n** (2 characters), not an actual newline. Since `newString` is written to the file without unescape processing, the literal `\n` characters end up in the file instead of actual newlines. ## Proposed Fix Extract `unescapeString()` from `EscapeNormalizedReplacer` and apply it to `newString` as well: ```typescript // edit.ts line 76 // Before: contentNew = replace(contentOld, params.oldString, params.newString, params.replaceAll) // After: const unescapedNewString = unescapeString(params.newString) contentNew = replace(contentOld, params.oldString, unescapedNewString, params.replaceAll) ``` ## Considerations - Need to test edge cases where literal `\\n` is intended (e.g., regex patterns) - Should add test cases for this scenario Would this approach be acceptable? I would like to work on this if it is still available.
Author
Owner

@z64 commented on GitHub (Feb 16, 2026):

I think I have also been getting bit by this using GPT-OSS-120B locally off of llama.cpp via Lemonade. It is pretty common that occasionally some edits will write the literal two characters \n instead of real newlines, wasting turns as it figures out what went wrong. No set of inference parameters that I've tried has completely fixed it.

I will see about testing #13106 locally to see if that fixes it, but I'm pretty out of my depth when it comes to the internals of this stuff, so extra eyes would be appreciated.

@z64 commented on GitHub (Feb 16, 2026): I think I have also been getting bit by this using GPT-OSS-120B locally off of llama.cpp via Lemonade. It is pretty common that occasionally some edits will write the literal two characters `\n` instead of real newlines, wasting turns as it figures out what went wrong. No set of inference parameters that I've tried has completely fixed it. I will see about testing #13106 locally to see if that fixes it, but I'm pretty out of my depth when it comes to the internals of this stuff, so extra eyes would be appreciated.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#2342