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
- Replace the symlink with a real copy of the file inside the repo, then re-apply the patch.
- Point the patch at the link's real target by making that target live under repo_root.
- 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
- Keep patchable source files as regular files, not links.
- Check is_symlink() on the joined path before resolve(), since resolve() hides links.
- In CI, run a repo lint that flags symlinks pointing outside the repo.
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
- Absolute paths are not allowed: {rel_path}
- Path escapes repo_root: {rel_path}
- Unknown op kind: {kind}
- Disallowed file suffix for write: {target.suffix}
- Patch must start with '*** Begin Patch'
AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14).
Data as JSON: /api/errors/52ee2f017652cd33.
Report an issue: GitHub.