oraios/serena · error · ValueError
The content of {path} changed while replacing (occurrence {e
Error message
The content of {path} changed while replacing (occurrence {e} no longer resolves); the file was NOT modified. Re-run with dry_run=True for current ids. What it means
During _apply_occurrences, Serena re-derives occurrences from the authoritative file content (to tolerate line-ending normalization) and re-validates the previously selected occurrences by id. If an id no longer resolves, the file content changed concurrently; the tool raises this ValueError and leaves the file unmodified rather than producing a corrupted edit.
Source
Thrown at src/serena/tools/file_tools.py:441
needle: str,
repl: str,
) -> str:
occurrences_by_file: dict[str, list[ReplacementOccurrence]] = {}
for occ in occurrences:
occurrences_by_file.setdefault(occ.relative_path, []).append(occ)
with self.DiagnosticsContext(self, *occurrences_by_file.keys()) as diagnostics_context:
code_editor = self.create_code_editor()
for path, file_occurrences in occurrences_by_file.items():
with EditedFileContext(path, code_editor) as context:
original_content = context.get_original_content()
if original_content != contents[path]:
# the editor's view differs from what was scanned (e.g. line-ending normalization);
# re-derive the occurrences from the authoritative content and re-validate by id
fresh_by_id = {o.occurrence_id: o for o in replacer.find_occurrences([(path, original_content)], needle, repl)}
try:
file_occurrences = [fresh_by_id[o.occurrence_id] for o in file_occurrences]
except KeyError as e:
raise ValueError(
f"The content of {path} changed while replacing (occurrence {e} no longer resolves); "
f"the file was NOT modified. Re-run with dry_run=True for current ids."
) from e
context.set_updated_content(replacer.apply_to_content(original_content, file_occurrences))
per_file = "\n".join(f" {path}: {len(occs)}" for path, occs in occurrences_by_file.items())
summary = f"Replaced {len(occurrences)} occurrence(s) in {len(occurrences_by_file)} file(s):\n{per_file}"
return diagnostics_context.format_result(summary)
class DeleteLinesTool(EditingToolWithDiagnostics, ToolMarkerOptional):
"""
Deletes a range of lines within a file.
"""
def apply(
self,
relative_path: str,
start_line: int,View on GitHub (pinned to 7fcbca7e62)
Solutions
- Re-run the whole operation with dry_run=True to obtain current occurrence ids, then apply with the fresh ids
- Ensure no formatter/autosave or concurrent process modifies the file during the replacement
- Retry after the external writer finishes; re-scan content first to confirm it is stable
- Apply replacements serially per file instead of in parallel with other editors
Example fix
// before
agent.replace_content(needle="foo", repl="bar", occurrence_ids=stale_ids)
# ValueError: content changed while replacing...
// after
dry = agent.replace_content(needle="foo", repl="bar", dry_run=True)
agent.replace_content(needle="foo", repl="bar",
occurrence_ids=[o.id for o in dry.occurrences]) Defensive patterns
Strategy: retry
Validate before calling
content_before = open(abs_path, 'rb').read() # ... run replacement ... content_after = open(abs_path, 'rb').read() assert content_before == content_after or replacement_succeeded
Type guard
def file_stable(path: str, settle_secs: float = 0.2) -> bool:
import time
a = open(path, 'rb').read(); time.sleep(settle_secs)
return open(path, 'rb').read() == a Try / catch
try:
agent.replace_content(needle, repl, occurrence_ids=ids)
except ValueError as e:
if "changed while replacing" in str(e):
dry = agent.replace_content(needle, repl, dry_run=True)
agent.replace_content(needle, repl, occurrence_ids=[o.id for o in dry.occurrences])
else:
raise Prevention
- Disable format-on-save/autosave during automated replacements
- Serialize file edits across agents and processes
- Always refresh occurrence ids from a dry run right before applying
When it happens
Trigger: The file was edited (manually, by an editor with autosave/format-on-save, or by another agent/process) between the scan and the apply; line-ending conversion altered the content beyond what normalization can reconcile; occurrence ids from a stale dry run are replayed against changed content.
Common situations: Agents running replacements while a language server or formatter rewrites files; concurrent automation pipelines touching the same file; applying replacements in a worktree while an IDE buffer is being saved.
Related errors
- Config overview not yet available
- {len(problems)} of the given occurrence_ids could not be res
- occurrence_ids is empty - pass at least one id from a dry ru
- No occurrences of the pattern were found - NO changes were a
- expected_count={expected_count}, but the pattern matches {le
AI-assisted analysis of oraios/serena@7fcbca7e62 (2026-08-29).
Data as JSON: /api/errors/061e3e053afc5e9d.
Report an issue: GitHub.