datawhalechina/hello-agents · error · PatchApplyError

Refusing to modify symlink: {rel_path}

Error message

Refusing to modify symlink: {rel_path}

What it means

Raised by _safe_path when the computed target exists and is a symlink — the executor refuses to modify symlinks so a patch cannot silently rewrite a file outside the repo through a link. In practice this branch is nearly dead code because target comes from Path.resolve(), which already follows symlinks; only dangling symlinks (which resolve() does not chase on non-strict mode... it keeps the path) or unusual platforms can reach it.

Source

Thrown at Co-creation-projects/YYHDBL-HelloCodeAgentCli/code_agent/executors/apply_patch_executor.py:206

        确保目标路径在 repo_root 目录下,防止访问仓库外的文件。
        
        参数:
            rel_path: 相对路径字符串
            
        返回:
            Path: 安全的绝对路径对象
            
        异常:
            PatchApplyError: 当路径是绝对路径、包含特殊字符或试图访问仓库外时抛出
        """
        if rel_path.startswith("/") or rel_path.startswith("~"):
            raise PatchApplyError(f"Absolute paths are not allowed: {rel_path}")
        target = (self.repo_root / rel_path).resolve()
        # 检查解析后的路径是否以 repo_root 开头
        if not str(target).startswith(str(self.repo_root.resolve()) + os.sep) and target != self.repo_root.resolve():
            raise PatchApplyError(f"Path escapes repo_root: {rel_path}")
        if target.exists() and target.is_symlink():
            raise PatchApplyError(f"Refusing to modify symlink: {rel_path}")
        return target

    def _enforce_suffix(self, target: Path) -> None:
        """
        检查目标文件的后缀是否在允许的列表中。
        防止意外修改二进制文件、配置文件或其他敏感文件。
        
        参数:
            target: 目标文件路径对象
            
        异常:
            PatchApplyError: 当文件后缀不在允许列表中时抛出
        """
        if target.suffix and target.suffix not in self.allowed_write_suffixes:
            raise PatchApplyError(f"Disallowed file suffix for write: {target.suffix}")

    def _backup_file(self, target: Path, backup_run_dir: Path) -> Path:
        """

View on GitHub (pinned to 606a07d341)

Solutions

  1. Replace the symlink with a real copy of the file inside the repo, then re-apply the patch.
  2. Point the patch at the link's real target by making that target live under repo_root.
  3. If you maintain the executor, check for symlinks before resolve() (on the joined path) so the guard is actually reachable and meaningful.

Example fix

# before (guard runs after resolve(), mostly unreachable)
target = (self.repo_root / rel_path).resolve()
if target.exists() and target.is_symlink(): ...

# after (check the link before resolving)
joined = self.repo_root / rel_path
if joined.is_symlink():
    raise PatchApplyError(f"Refusing to modify symlink: {rel_path}")
target = joined.resolve()
Defensive patterns

Strategy: validation

Validate before calling

joined = repo_root / rel_path
if joined.is_symlink():
    real = joined.resolve()
    raise ValueError(f'{rel_path} is a symlink to {real}; patch the real file instead')

Type guard

def is_plain_file_in_repo(rel_path: str, repo_root: Path) -> bool:
    joined = repo_root / rel_path
    return not joined.is_symlink() and str(joined.resolve()).startswith(str(repo_root.resolve()) + os.sep)

Try / catch

try:
    executor.apply(patch)
except PatchApplyError as e:
    if 'Refusing to modify symlink' in str(e):
        # resolve the link target and re-issue the patch against the real path inside the repo
        ...

Prevention

When it happens

Trigger: rel_path names a dangling symlink inside the repo, or an environment where resolve() leaves the link unresolved; writing through a symlink whose target sits outside repo_root would otherwise defeat the escape check that runs earlier.

Common situations: Repos with symlinked config files or node_modules-style links; CI checkouts that materialize some paths as links; attempting to patch a file that is a link into a shared volume.

Related errors


AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14). Data as JSON: /api/errors/52ee2f017652cd33. Report an issue: GitHub.