microsoft / microsoft/vscode

Copilot edit tools report success even when the underlying FileSystemProvider.writeFile fails

Open
#331,790 0 comments 0 reactions 1 assignee Claimed by @roblourens View on GitHub
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

## Description

We maintain a VS Code extension that registers a custom `FileSystemProvider` for a virtual scheme. The provider forwards each save to a remote backend system in `writeFile()`. When that backend rejects the save, `writeFile()` throws a `vscode.FileSystemError` (e.g. `FileSystemError.Unavailable`), and VS Code core surfaces this correctly to the user (error notification, the editor stays dirty).

However, when a file is edited by a **Copilot edit tool** (`replace_string_in_file`, `apply_patch`, `insert_edit_into_file` → `vscode_editFile_internal`, etc.) rather than by the human, the tool reports the file as successfully **"Updated"** even though the backend save failed and nothing was persisted. **The failure is completely invisible to the agent.**

## Root cause (from reading the extension source)

The edit tools apply their changes by **streaming** text edits into the chat-editing session via `ChatResponseStream.textEdit(uri, edit)`. They never call `document.save()` / `workspace.applyEdit()` themselves, and they never inspect the result of the eventual save.

The actual save happens later, downstream in VS Code core, where our `writeFile()` throws — but that throw is entirely disconnected from the tool's result path. So the tool's *"successfully edited"* result is decided **before** (and independently of) whether the save succeeded.

The only post-edit signal the edit tool reads back is **diagnostics** (it re-reads diagnostics for the edited document and feeds new ones to the model). A `FileSystemError` thrown from `writeFile()` is not part of that channel, so it is never observed.

## Impact

Any extension whose `FileSystemProvider` can legitimately fail a save — remote/networked backends, locked resources, permission/authorization checks, quota, etc. — will have those failures **silently swallowed** when edits come from an agent. The agent believes its change landed and continues building on a state that was never persisted.

## Workaround we're using

We publish an **error diagnostic** on the file when the backend save fails (and clear it on the next successful save). Because the edit tool re-reads diagnostics, this makes the failure *observable* — but only partially:

- **Higher reasoning effort:** the agent proactively re-checks diagnostics after each edit and notices the failure.
- **Default settings:** it often does *not* re-check, and still reports the edit as fine.

So the diagnostic is a strict improvement over invisible failures, but it isn't a reliable fix — it relies on the agent *choosing* to look.

## Suggested fix

Have the edit tools observe the outcome of the save they trigger — e.g. **await the save and propagate a `FileSystemProvider.writeFile` rejection into the tool result** (report the edit as failed / not applied) instead of reporting success unconditionally. That way a failed save is surfaced **deterministically**, independent of the model's reasoning effort.

---

## Environment

- **VS Code Version:** 1.134.0 (commit `110a328ea54b42367b803ec53ee0bf52ef26b419`, x64) — stable
- **OS Version:** Windows 11, 10.0.26200.8875 (x64)
- **Feature (agent/edit/ask mode):** agent mode (edit tools: `replace_string_in_file` / `apply_patch` / `insert_edit_into_file`)
- **Selected model:** Claude Sonnet 5 and Claude Opus 4.8 (behavior differs by reasoning effort — see *Workaround* above)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.