fix: prevent self-referential symlinks in sync_scripts_to_workspaces

When install.sh link_resources() creates workspace-*/scripts as a
directory-level symlink pointing to the project scripts/ dir, iterating
over it in sync_scripts_to_workspaces() produces dst_file paths that
resolve to the same real file as src_file.

The old idempotency check only skipped when dst_file itself was already a
symlink:

    if dst_file.is_symlink() and dst_file.resolve() == src_resolved:
        return False

For a workspace whose scripts/ directory is a symlink-to-directory,
dst_file appears as a regular file (is_symlink() == False), so the check
passes, the real source file is unlinked, and os.symlink() re-creates it
as a self-referential link (foo.py -> foo.py).  Running run_loop.sh every
15 s makes the whole scripts/ directory unusable within one cycle.

Fix: resolve dst_file before any other check and bail out early when
dst_resolved == src_resolved, regardless of whether dst_file itself is
stored as a symlink entry.
This commit is contained in:
YueKang
2026-03-28 16:47:38 +08:00
parent 18c15209ca
commit 385c8d2ddf

View File

@@ -222,8 +222,19 @@ def _sync_script_symlink(src_file: pathlib.Path, dst_file: pathlib.Path) -> bool
Returns True if the link was (re-)created, False if already up-to-date.
"""
src_resolved = src_file.resolve()
# Guard: skip if dst resolves to the same real path as src.
# This happens when ws_scripts is itself a directory-level symlink pointing
# to the project scripts/ dir (created by install.sh link_resources).
# Without this check the function would unlink the real source file and
# then create a self-referential symlink (foo.py -> foo.py).
try:
dst_resolved = dst_file.resolve()
except OSError:
dst_resolved = None
if dst_resolved == src_resolved:
return False
# Already a correct symlink?
if dst_file.is_symlink() and dst_file.resolve() == src_resolved:
if dst_file.is_symlink() and dst_resolved == src_resolved:
return False
# Remove stale file / old physical copy / broken symlink
if dst_file.exists() or dst_file.is_symlink():