Repository navigation
BUG: a failed file write is reported to the model as "I updated the files." and counted as an edit #5879
Description
Issue
Summary
When writing an edit to disk fails with an OSError (for example PermissionError on a read-only file), Aider prints the write error but then treats the file as edited: it adds the path to aider_edited_files and appends "I updated the files." / "Ok." to the chat history. The file on disk is unchanged, so the next turn tells the model that an edit was applied when it was not. No reflection is sent, so the model gets no chance to react to the failure.
Steps to reproduce
pip install aider-chat==0.86.0(no API key or network needed; the model reply is scripted).- Save as
repro.py:
# A failed write is reported to the model as "I updated the files." and counted as an edit.
# Requires only `pip install aider-chat==0.86.0`; no API key or network (litellm.completion is scripted).
import os
import stat
import tempfile
from pathlib import Path
import litellm
import aider
from aider.coders import Coder
from aider.io import InputOutput
from aider.models import Model
print("aider", aider.__version__)
def completion(**kwargs):
return litellm.ModelResponse(
model=kwargs["model"],
choices=[{"index": 0, "finish_reason": "stop",
"message": {"role": "assistant", "content": "settings.py\n```\nVALUE = 2\n```\n"}}],
usage={"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
)
litellm.completion = completion
root = Path(tempfile.mkdtemp())
os.chdir(root)
target = root / "settings.py"
target.write_text("VALUE = 1\n")
target.chmod(stat.S_IRUSR) # read-only: the write will fail with PermissionError
io = InputOutput(pretty=False, fancy_input=False, yes=True)
coder = Coder.create(main_model=Model("gpt-4o"), io=io, fnames=[str(target)], edit_format="whole",
use_git=False, stream=False, map_tokens=0, suggest_shell_commands=False)
coder.run(with_message="Set VALUE to 2.")
print("--- after the run")
print("file content:", repr(target.read_text()))
print("aider_edited_files:", sorted(coder.aider_edited_files))
print("last messages in history:", [(m["role"], m["content"]) for m in coder.done_messages[-2:]])- Run
OPENAI_API_KEY=sk-none python repro.pyas a non-root user (root ignores the read-only bit).
Actual output
aider 0.86.0
settings.py
```diff
@@ -1 +1 @@
-VALUE = 1
+VALUE = 2
```
Tokens: 1 sent, 1 received. Cost: $0.000013 message, $0.000013 session.
Unable to write file /tmp/tmpXXXX/settings.py after 5 attempts: [Errno 13]
Permission denied: '/tmp/tmpXXXX/settings.py'
[Errno 13] Permission denied: '/tmp/tmpXXXX/settings.py'
--- after the run
file content: 'VALUE = 1\n'
aider_edited_files: ['settings.py']
last messages in history: [('user', 'I updated the files.'), ('assistant', 'Ok.')]
main at 5dc9490b prints the same (only the version line and temp paths differ). The same happens for other OSErrors raised while writing, e.g. when the target path has been replaced by a directory.
Expected behavior
A failed write should not be recorded as a successful edit. Either the error should be reflected to the model (as the generic except Exception branch of apply_updates already does via reflected_message), or at least the file should not be added to the edited set and "I updated the files." should not be added to the history.
Root cause
apply_updates computes edited from the parsed edits before writing, and its except ANY_GIT_ERROR branch returns that set after only printing the error. ANY_GIT_ERROR includes OSError (repo.py L26-L35), so a write failure from io.write_text takes this branch. send_message then treats the returned set as applied edits: it updates aider_edited_files, auto-commits (in a repo), and moves files_content_gpt_edits_no_repo ("I updated the files.") into the history.
Possible fix direction
Return an empty set (or let the write failure reach the generic branch so reflected_message is set) when the write itself fails, so only files that were actually written are reported as edited.
Version and model info
Aider: 0.86.0 (PyPI) and main@5dc9490bb35f9729ef2c95d00a19ccd30c26339c (0.86.3.dev0)
Model: gpt-4o with litellm.completion scripted (no real model call)
Edit format: whole; no git repo
Python: 3.12.13
Operating system: Ubuntu 22.04.5 LTS
Activity