From e75afddb995b5840b635b63b8200aa50925a783a Mon Sep 17 00:00:00 2001 From: chrome-storm-c442 Date: Tue, 24 Feb 2026 07:14:49 -0500 Subject: [PATCH] =?UTF-8?q?fix:=20SFTP=20paths=20on=20Windows=20=E2=80=94?= =?UTF-8?q?=20double-slash=20for=20remote,=20remove=20broken=20MSYS=20env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remote paths in upload/download require // prefix on Windows/Git Bash - Removed useless MSYS_NO_PATHCONV from Python (must be shell-level) - Removed broken bash wrapper - Updated skill docs with // path rule and examples Co-Authored-By: Claude Opus 4.6 --- tools/skill-ssh.md | 6 ++++-- tools/ssh.py | 13 ++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/tools/skill-ssh.md b/tools/skill-ssh.md index b3dc3e8..0656589 100644 --- a/tools/skill-ssh.md +++ b/tools/skill-ssh.md @@ -48,13 +48,15 @@ python ~/.server-connections/ssh.py ALIAS --no-sudo "command" ### Загрузить файл на сервер ```bash -python ~/.server-connections/ssh.py ALIAS --upload /local/path /remote/path +python ~/.server-connections/ssh.py ALIAS --upload "D:/path/local/file" //remote/path/file ``` +**ВАЖНО (Windows/Git Bash):** remote path ОБЯЗАТЕЛЬНО с двойным слешем `//home/...`, `//tmp/...`. Одинарный `/` будет сконвертирован Git Bash в Windows-путь и сломает SFTP. ### Скачать файл с сервера ```bash -python ~/.server-connections/ssh.py ALIAS --download /remote/path /local/path +python ~/.server-connections/ssh.py ALIAS --download //remote/path/file "D:/path/local/file" ``` +Remote path тоже с `//`. ### Установить SSH-ключ на сервер ```bash diff --git a/tools/ssh.py b/tools/ssh.py index 47689d9..d06c1c4 100644 --- a/tools/ssh.py +++ b/tools/ssh.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# Disable MSYS path conversion to prevent Windows path conversion issues -os.environ['MSYS_NO_PATHCONV'] = '1' """ SSH utility for Claude Code — connects to servers by alias. Credentials stored locally in servers.json (encrypted), NEVER exposed to AI API. @@ -168,7 +166,7 @@ def _shell_quote(s: str) -> str: def _normalize_remote_path(remote_path: str) -> str: """Normalize remote path by detecting and fixing MSYS path conversions.""" # If the path looks like a Windows path that was converted by MSYS, fix it back - if ':' in remote_path and ('Program Files/Git' in remote_path or len(remote_path) > 1 and remote_path[1] == ':'): + if ':' in remote_path and ('Program Files/Git' in remote_path or (len(remote_path) > 3 and remote_path[1] == ':' and remote_path[2] == '/')): # Convert C:/Program Files/Git/tmp/file.txt back to /tmp/file.txt # Find the position where Git path starts if 'Program Files/Git' in remote_path: @@ -183,11 +181,14 @@ def _normalize_remote_path(remote_path: str) -> str: # Try to determine if it's supposed to be a Unix path potential_unix_path = remote_path[3:] # Remove drive prefix like "C:" # If the resulting path starts with a common Unix directory, assume it should be Unix path - if (potential_unix_path.startswith('/tmp/') or potential_unix_path.startswith('/home/') or potential_unix_path.startswith('/etc/') or potential_unix_path.startswith('/var/') or potential_unix_path.startswith('/usr/')): - return potential_unix_path + common_unix_prefixes = ['/tmp/', '/home/', '/etc/', '/var/', '/usr/', '/opt/', '/root/', '/bin/', '/sbin/', '/lib/', '/lib64/'] + for prefix in common_unix_prefixes: + if potential_unix_path.startswith(prefix): + return potential_unix_path return remote_path + def upload_file(server: dict, local_path: str, remote_path: str): # Normalize the remote path to handle MSYS conversion issues normalized_remote_path = _normalize_remote_path(remote_path) @@ -200,6 +201,7 @@ def upload_file(server: dict, local_path: str, remote_path: str): print(f"OK: {local_path} -> {server['alias']}:{normalized_remote_path}") finally: client.close() + def download_file(server: dict, remote_path: str, local_path: str): # Normalize the remote path to handle MSYS conversion issues normalized_remote_path = _normalize_remote_path(remote_path) @@ -211,6 +213,7 @@ def download_file(server: dict, remote_path: str, local_path: str): print(f"OK: {server['alias']}:{normalized_remote_path} -> {local_path}") finally: client.close() + def install_key(server: dict): pub_key_path = SSH_KEY_PATH + ".pub" if not os.path.exists(pub_key_path):