Before submitting your bug report
Relevant environment info
- OS:Windows 11 Enterprise
- Continue version: 2.0.0
- IDE version: 1.138.0
- Model: Qwen3.8-27B
- config:
name: Main Config
version: 1.0.0
schema: v1
models:
- name: Qwen-Coder
provider: openai
model: Qwen-Coder
apiBase: http://172.19.128.237:8000/v1
apiKey: EMPTY
capabilities:
- tool_use
- image_input
roles:
- chat
- edit
- apply
- autocomplete
defaultCompletionOptions:
temperature: 1.0
top_p: 0.95
maxTokens: 8192
contextLength: 131072
reasoning: true
requestOptions:
stream: false
extraBodyProperties:
top_k: 20
min_p: 0.0
presence_penalty: 0.0
repetition_penalty: 1.0
reasoning_effort: "low" # "medium" "high"
OR link to agent in Continue hub:
Description
OS: Linux
Model/provider (repro): any agent-mode model via an OpenAI-compatible provider (repro'd with Qwen-Coder over vLLM)
Summary
When the agent calls the built-in edit_existing_file tool and the changes argument is a bare changed line (no // ... existing code ... anchors, no surrounding unchanged context), Continue treats that text as the entire new file and overwrites the target. The resulting diff shows the whole file deleted and a single line inserted. It returns Edit Success with no warning. Accepting it destroys the file.
Steps to reproduce
- In agent mode, request a one-line fix to a file larger than a few lines (e.g. fix a missing function argument).
- The model emits
edit_existing_file with only the changed line, e.g.:
{"filepath": "word_generator.py",
"changes": " words = find_words(letters, trie, args.min_freq, max_len, args.min_len)"}
- Continue renders a diff of delete all N lines → insert 1 line.
- Click Accept → the file is reduced to that single line.
Actual behavior
- Anchor-less
changes payload ⇒ whole-file replacement.
- No validation, no warning, no confirmation.
single_find_and_replace on the same file is unaffected: it does exact string substitution and snapshots editingFileContents/newFileContents in processedArgs, producing a correct one-line diff.
Expected behavior
When changes contains no anchor markers (// ... existing code ... etc.) and the target file is large, Continue should refuse the edit or force an explicit confirmation rather than defaulting to full-file overwrite. A bare snippet must never be accepted as the complete new file content without guardrails.
Root cause (from session log 38253ff2-3389-49f3-9b95-5fa12aa37c63.json)
- Trigger (model layer, outside Continue): model emitted a bare
changes line with no anchors. The tool schema documents anchors as the mechanism for reconstructing the kept portions of the file.
- Amplifier (Continue tool layer, the bug):
edit_existing_file has no match; with nothing to anchor against it falls back to "provided text == entire file" and overwrites. This unsafe fallback is what makes a formatting slip data-destructive.
- Exonerated: the inference/transport provider (vLLM) parsed the tool call correctly and delivered the model output intact; the overwrite is decided entirely by Continue's tool implementation. No inference-side config can fix it.
Proposed fix
- In
edit_existing_file, detect anchor-less changes on a non-trivial file and reject with a descriptive error (or require an explicit user confirmation) instead of full-replacing.
- At minimum, surface a warning on the diff when a proposed edit removes > ~50% of the file.
To reproduce
Steps to reproduce (Continue agent mode, any model via an OpenAI-compatible provider)
Precondition: a workspace file that is substantially longer than the edit — e.g. word_generator.py (~186 lines) containing a one-arg bug:
words = find_words(letters, trie, args.min_freq, max_len) # missing args.min_len
- Open Continue in agent mode, with the terminal showing a traceback pointing at that line.
- Send a message that (a) includes the terminal error as context and (b) frames it as a small targeted fix, e.g. "fix the TypeError shown in the terminal."
- The agent calls
read_file on word_generator.py, then calls edit_existing_file with a bare, un-anchored changes payload (the model does not emit // ... existing code ... or surrounding context):
{"filepath": "word_generator.py",
"changes": " words = find_words(letters, trie, args.min_freq, max_len, args.min_len)"}
- Continue returns
Edit Success and renders a diff showing the entire file deleted and one line inserted (the full-file overwrite).
- Click Accept on that diff.
Result: word_generator.py is reduced to the single line — the rest of the file is deleted. No warning was shown before Accept.
Determining factor (why it's the tool, not the model):
Repeat the identical one-line change using single_find_and_replace with old_string → new_string. It produces a correct one-line diff and preserves the file. The difference is that edit_existing_file, given an anchor-less payload, silently defaults to full-file replacement, whereas single_find_and_replace is exact-match and cannot.
Minimal trigger condition: any edit_existing_file call where changes contains no anchor markers and no surrounding unchanged lines, applied to a file longer than the snippet.
Log output
Here are the exact fragments from `~/.continue/sessions/38253ff2-3389-49f3-9b95-5fa12aa37c63.json` that establish the root cause. I'm quoting them verbatim from the log (I re-read the file; the two decisive records are the `toolCallStates` entries for tool ids `chatcmpl-tool-aee29c1bd61b73c4` and `chatcmpl-tool-96613c2d7465de2b`).
### 1. The destructive call — `edit_existing_file` (tool id `chatcmpl-tool-aee29c1bd61b73c4`)
The model's emitted payload (`parsedArgs`) is a **bare one-line `changes` with no anchors**:
"parsedArgs": {
"filepath": "word_generator.py",
"changes": " words = find_words(letters, trie, args.min_freq, max_len, args.min_len)"
}
Note there is **no** `processedArgs` block with `editingFileContents`/`newFileContents` here — unlike the safe tool below, Continue did **not** snapshot the full before/after, because it treated the bare line as the whole new file. The result:
"output": [
{
"name": "Edit Success",
"content": "Successfully edited file:///home/u121988/continue_coder_test/word_generator.py",
"description": "",
"hidden": true
}
]
### 2. The contrasting safe call — `single_find_and_replace` (tool id `chatcmpl-tool-96613c2d7465de2b`)
This one **does** carry the full snapshots in `processedArgs`, proving the tool layer *can* and *did* reconstruct the file correctly when it isn't forced to guess:
"processedArgs": {
"filepath": "word_generator.py",
"old_string": " words = find_words(letters, trie, args.min_freq, max_len)",
"new_string": " words = find_words(letters, trie, args.min_freq, max_len, args.min_len)",
"fileUri": "file:///home/u121988/continue_coder_test/word_generator.py",
"editingFileContents": "#!/usr/bin/env python3\n\"\"\"Generate all valid words ...", // full ~186-line original
"newFileContents": "#!/usr/bin/env python3\n\"\"\"Generate all valid words ..." // full file, only line 154 differs
}
"output": [
{ "name": "Edit Success",
"content": "Successfully edited file:///home/u121988/continue_coder_test/word_generator.py" }
]
### 3. The model output as delivered by the transport (proves vLLM is not the culprit)
From `promptLogs[0].completion`, the streamed tool-call tokens show vLLM delivered **exactly** the bare line the model produced — it neither anchored nor overwrote anything:
<assistant>
edit_existing_file(undefined)
<assistant>
undefined({"filepath": ")
...
undefined( words =)
...
undefined( find_words()
...
undefined(.min_len))
<assistant>
undefined("})
Reassembled, this is precisely the payload in section 1. The transport parsed and streamed it faithfully; the overwrite was decided downstream by Continue.
---
### What this proves, categorically
- **Trigger (model layer):** `changes` was a bare line with no `// ... ... existing code ...` anchors and no surrounding unchanged context (section 1).
- **Amplifier / bug (Continue tool layer):** `edit_existing_file`, given that anchor-less payload, had nothing to match, so it defaulted to "provided text == entire new file" — hence the whole-file deletion in your screenshot — and returned `Edit Success` with **no full-file snapshot recorded** and **no warning** (section 1).
- **Control (proves it's tool-specific, not model-specific):** the *same* logical edit via `single_find_and_replace` recorded complete `editingFileContents`/`newFileContents` and produced a correct one-line diff (section 2).
- **Transport exonerated:** vLLM delivered the model's output intact and correctly parsed the tool call (section 3). The destructive decision is made entirely in Continue's `edit_existing_file` implementation.
These are the three fragments to paste under the **Root cause** section of the issue.
Before submitting your bug report
Relevant environment info
Description
OS: Linux
Model/provider (repro): any agent-mode model via an OpenAI-compatible provider (repro'd with Qwen-Coder over vLLM)
Summary
When the agent calls the built-in
edit_existing_filetool and thechangesargument is a bare changed line (no// ... existing code ...anchors, no surrounding unchanged context), Continue treats that text as the entire new file and overwrites the target. The resulting diff shows the whole file deleted and a single line inserted. It returnsEdit Successwith no warning. Accepting it destroys the file.Steps to reproduce
edit_existing_filewith only the changed line, e.g.:{"filepath": "word_generator.py", "changes": " words = find_words(letters, trie, args.min_freq, max_len, args.min_len)"}Actual behavior
changespayload ⇒ whole-file replacement.single_find_and_replaceon the same file is unaffected: it does exact string substitution and snapshotseditingFileContents/newFileContentsinprocessedArgs, producing a correct one-line diff.Expected behavior
When
changescontains no anchor markers (// ... existing code ...etc.) and the target file is large, Continue should refuse the edit or force an explicit confirmation rather than defaulting to full-file overwrite. A bare snippet must never be accepted as the complete new file content without guardrails.Root cause (from session log
38253ff2-3389-49f3-9b95-5fa12aa37c63.json)changesline with no anchors. The tool schema documents anchors as the mechanism for reconstructing the kept portions of the file.edit_existing_filehas no match; with nothing to anchor against it falls back to "provided text == entire file" and overwrites. This unsafe fallback is what makes a formatting slip data-destructive.Proposed fix
edit_existing_file, detect anchor-lesschangeson a non-trivial file and reject with a descriptive error (or require an explicit user confirmation) instead of full-replacing.To reproduce
Steps to reproduce (Continue agent mode, any model via an OpenAI-compatible provider)
Precondition: a workspace file that is substantially longer than the edit — e.g.
word_generator.py(~186 lines) containing a one-arg bug:read_fileonword_generator.py, then callsedit_existing_filewith a bare, un-anchoredchangespayload (the model does not emit// ... existing code ...or surrounding context):{"filepath": "word_generator.py", "changes": " words = find_words(letters, trie, args.min_freq, max_len, args.min_len)"}Edit Successand renders a diff showing the entire file deleted and one line inserted (the full-file overwrite).Result:
word_generator.pyis reduced to the single line — the rest of the file is deleted. No warning was shown before Accept.Determining factor (why it's the tool, not the model):
Repeat the identical one-line change using
single_find_and_replacewithold_string→new_string. It produces a correct one-line diff and preserves the file. The difference is thatedit_existing_file, given an anchor-less payload, silently defaults to full-file replacement, whereassingle_find_and_replaceis exact-match and cannot.Minimal trigger condition: any
edit_existing_filecall wherechangescontains no anchor markers and no surrounding unchanged lines, applied to a file longer than the snippet.Log output