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 path

View on GitHub (pinned to 7fcbca7e62)

Solutions

  1. Pass a path relative to the project root, without `..` segments.
  2. Copy the external file into the project if it must be edited with this editor.
  3. Use a separate CodeEditor instance rooted at the other project/directory.
  4. 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

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


AI-assisted analysis of oraios/serena@7fcbca7e62 (2026-08-29). Data as JSON: /api/errors/b4b9c3a4171c6ab9. Report an issue: GitHub.