oraios/serena · error · ValueError
Cannot edit external file: {relative_path}
Error message
Cannot edit external file: {relative_path} What it means
edited_file_context refuses to open files outside the project root. FileProxy.is_external_path detects relative paths that escape the project (e.g. via `..`) and raises ValueError, since the editor only manages files within its project root.
Source
Thrown at src/serena/code_editor.py:83
:param relative_path: the relative path of the file to read
:param lines: tuple of (first_line, last_line) to read only a specific line range (0-based, inclusive)
:return: the content of the file
"""
with self._open_file_context(relative_path) as file:
contents = file.get_contents()
if lines is None:
return contents
else:
first_line, last_line = lines
return TextUtils.get_text_in_lines_range(contents, first_line, last_line)
@contextmanager
def edited_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
"""
Context manager for editing a file.
"""
if FileProxy.is_external_path(relative_path):
raise ValueError(f"Cannot edit external file: {relative_path}")
with self._open_file_context(relative_path) as edited_file:
yield edited_file
# save the file
self._save_edited_file(edited_file)
def _save_edited_file(self, edited_file: "CodeEditor.EditedFile") -> None:
abs_path = os.path.join(self.project_root, edited_file.relative_path)
new_contents = edited_file.get_contents()
with open(abs_path, "w", encoding=self.encoding, newline=self.newline) as f:
f.write(new_contents)
@abstractmethod
def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> TSymbol:
"""
Finds the unique symbol with the given name in the given file.
If no such symbol exists, raises a ValueError.
:param name_path: the name pathView on GitHub (pinned to 7fcbca7e62)
Solutions
- Pass a path relative to the project root, without `..` segments.
- Copy the external file into the project if it must be edited with this editor.
- Use a separate CodeEditor instance rooted at the other project/directory.
- Validate/normalize user-supplied paths against the project root before editing.
Example fix
// before
with editor.edited_file_context("../shared/util.py") as f: ...
// after
with editor.edited_file_context("src/shared/util.py") as f: ... Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def is_inside_project(rel: str, root: Path) -> bool:
p = (root / rel).resolve()
return p.is_relative_to(root.resolve()) and p.is_file()
if not is_inside_project(user_path, project_root):
raise ValueError(f'{user_path} is outside the project; refusing to edit') Try / catch
try:
with editor.edited_file_context(rel_path) as f:
f.replace_body(...)
except ValueError as e:
if str(e).startswith('Cannot edit external file'):
print(f'{rel_path} must be inside the project root') Prevention
- Always pass project-root-relative paths without `..` segments.
- Sanitize LLM/user-supplied paths against the project root before editing.
- Use a separate editor instance rooted elsewhere for out-of-project files.
When it happens
Trigger: Calling edited_file_context (or its callers: replace_body, insert_after/before_symbol, insert_at_line, delete_lines, delete_symbol) with a path like `../other/file.py`, an absolute path outside the root, or a symlink-resolved external path.
Common situations: Tool/LLM-supplied paths pointing at files outside the project; scripts operating on sibling directories; path traversal from user input; accidentally passing absolute paths to an editor rooted at the project.
Related errors
- Memory name resolves outside the memories directory. Got: {'
- Memory name cannot contain '..' segments. Got: {name}
- Memory name cannot be absolute or contain empty path segment
- Unsafe archive member '{member_name}': path traversal is not
- Unsafe archive member '{member_name}': path escapes extracti
AI-assisted analysis of oraios/serena@7fcbca7e62 (2026-08-29).
Data as JSON: /api/errors/b4b9c3a4171c6ab9.
Report an issue: GitHub.