astral-sh/ruff · error
InternalError
InternalError
Error message
Attempted to add edits for a document that was already edited
What it means
While assembling a WorkspaceEdit, the server accumulates per-document edit sets and guards an invariant: at most one edit set per document URI. If set_edits_for_document is called twice for the same URI in the OptionalVersionedTextDocumentIdentifier-based documentChanges mode, it refuses with this InternalError instead of silently overwriting the earlier edits.
Source
Thrown at crates/ruff_server/src/edit.rs:146
Ok(())
}
/// Sets the edits made to a specific document. This should only be called
/// once for each document `uri`, and will fail if this is called for the same `uri`
/// multiple times.
pub(crate) fn set_edits_for_document(
&mut self,
uri: Uri,
_version: DocumentVersion,
edits: Vec<lsp_types::TextEdit>,
) -> crate::Result<()> {
match self {
Self::DocumentChanges(document_edits) => {
if document_edits
.iter()
.any(|document| document.text_document.text_document_identifier.uri == uri)
{
return Err(anyhow::anyhow!(
"Attempted to add edits for a document that was already edited"
));
}
document_edits.push(lsp_types::TextDocumentEdit {
text_document: lsp_types::OptionalVersionedTextDocumentIdentifier {
text_document_identifier: TextDocumentIdentifier { uri },
// TODO(jane): Re-enable versioned edits after investigating whether it could work with notebook cells
version: None,
},
edits: edits.into_iter().map(lsp_types::Edit::TextEdit).collect(),
});
Ok(())
}
Self::Changes(changes) => {
if changes.get(&uri).is_some() {
return Err(anyhow::anyhow!(
"Attempted to add edits for a document that was already edited"
));View on GitHub (pinned to d1087a4b9e)
Solutions
- Restart the language server / reload the window to clear in-flight edit state
- Update to the latest Ruff server — duplicate-edit aggregation bugs get patched
- Trigger the operations separately (autofix, then organize imports) instead of combined until fixed
- If reproducible, capture tracing logs and file an issue with the command sequence
Defensive patterns
Strategy: validation
Validate before calling
// Server-side (Rust): merge edits per URI instead of adding a second set
use std::collections::BTreeMap;
let mut per_uri: BTreeMap<Uri, Vec<lsp_types::TextEdit>> = BTreeMap::new();
for (uri, edits) in incoming {
per_uri.entry(uri).or_default().extend(edits);
}
for (uri, edits) in per_uri {
edit_tracker.set_edits_for_document(uri, version, edits)?; // called once per URI
} Try / catch
// Rust: a duplicate-edit failure is recoverable — log, keep the first edit set, continue
if let Err(e) = tracker.set_edits_for_document(uri, version, edits) {
tracing::warn!(%e, %uri, "skipping duplicate edit set");
} Prevention
- Aggregate all edits into one map keyed by URI before building the WorkspaceEdit
- Apply combined operations sequentially (fix, then organize) rather than in one command
- Report reproducible duplicate-edit sequences upstream with tracing logs
When it happens
Trigger: A code path that applies two fix sources (e.g., fix-all plus organize-imports in one command, or a fix touching a notebook and its cell) both producing edits for the same file; duplicate resolve calls for the same document within one edit; regressions in the server's edit aggregation.
Common situations: Bugs in specific Ruff server versions combining commands on the same document; notebook workflows where both notebook-level and cell-level edits target one URI; races between concurrent fix requests on one file.
Related errors
AI-assisted analysis of astral-sh/ruff@d1087a4b9e (2026-08-20).
Data as JSON: /api/errors/29f9e4e8a1f31a61.
Report an issue: GitHub.