From dfe6fbb0b3fbfbbfe396e3248eab4ae4a54a89cb Mon Sep 17 00:00:00 2001 From: kewe63 Date: Mon, 13 Apr 2026 15:20:17 +0300 Subject: [PATCH] fix(ssh): narrow symlink fallback to WinError 1314 only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous catch-all except OSError would silently swallow real errors (disk full, bad path, permission issues unrelated to symlink privilege). Narrow the handler to winerror == 1314 — the specific Windows error code for "A required privilege is not held by the client" — and re-raise every other OSError so genuine failures are not hidden. --- tools/environments/ssh.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tools/environments/ssh.py b/tools/environments/ssh.py index fac9d5d6c..509e88ef8 100644 --- a/tools/environments/ssh.py +++ b/tools/environments/ssh.py @@ -179,8 +179,10 @@ class SSHEnvironment(BaseEnvironment): raise RuntimeError(f"remote mkdir failed: {result.stderr.strip()}") # Symlink staging avoids fragile GNU tar --transform rules. - # On Windows, symlink creation requires admin rights or Developer Mode, - # so fall back to copying the file when os.symlink raises OSError. + # On Windows without Developer Mode, symlink creation raises + # OSError with winerror 1314 (privilege not held). Catch only + # that specific error and fall back to a plain copy; all other + # OSErrors (e.g. disk full, bad path) are re-raised as normal. with tempfile.TemporaryDirectory(prefix="hermes-ssh-bulk-") as staging: for host_path, remote_path in files: try: @@ -199,8 +201,12 @@ class SSHEnvironment(BaseEnvironment): os.makedirs(os.path.dirname(staged), exist_ok=True) try: os.symlink(os.path.abspath(host_path), staged) - except OSError: - shutil.copy2(host_path, staged) + except OSError as e: + # WinError 1314: symlink privilege not held (Windows without Dev Mode) + if getattr(e, "winerror", None) == 1314: + shutil.copy2(host_path, staged) + else: + raise tar_cmd = ["tar", "-chf", "-", "-C", staging, "."] ssh_cmd = self._build_ssh_command()