[BUG] Drag & drop attachment (“Drop to attach as context”) does nothing in Cursor Remote-SSH (Windows → Debian 11)

Status Closed — not planned
Reported on v2.1.19
Maintainer reply None cached
Activity 6 comments · opened Jan 26, 2026 · closed Mar 3, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Shift + drag & drop into the Claude Code input works only visually in Cursor Remote-SSH: the UI shows “Drop to attach as context”, but when I drop the file nothing is attached (no file/path appears, no context is added, no error shown). This happens only when connected via Remote-SSH. On a local Windows workspace it works.

What Should Happen?

When I hold Shift and drop a file onto the Claude Code prompt input, the file should be attached as context (or its path should be inserted / an attachment chip should appear), so Claude can read it as part of the conversation context.

Error Messages/Logs

Steps to Reproduce

  1. On Windows 11, open Cursor 2.3.30 (VS Code base 1.105.1).
  2. Connect to a Debian 11 VPS using Remote-SSH.
  3. Open the Claude Code panel/chat (extension 2.1.19).
  4. Hold Shift and drag any file from the Explorer (or an open editor tab) over the Claude Code prompt input.
  5. Confirm the overlay appears: “Drop to attach as context”.
  6. Drop the file.

Claude Model

None

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.19 (Claude Code)

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Cursor

Additional Information

  • This issue happens only in Remote-SSH sessions. The same Shift + drag & drop workflow works in a local (non-SSH) workspace on the same Windows 11 machine.
  • The Claude Code UI correctly detects the drag operation (overlay shows “Drop to attach as context”), but the drop action is ignored (no attachment/path/context is added, no error shown). This suggests the drag-over handler works, but the drop handler fails in Remote-SSH.
  • OpenAI Codex drag & drop works in the same Cursor Remote-SSH environment, so the problem seems specific to Claude Code’s drag/drop handling under Remote-SSH rather than Cursor/Remote-SSH generally.
  • Environment details:
  • Client: Windows 11
  • Remote VPS: Debian 11
  • Cursor 2.3.30 (VS Code base 1.105.1)
  • Claude Code extension 2.1.19
  • Possible cause (hypothesis): Remote workspaces often use remote URI/path formats (e.g. vscode-remote://...) and the drop payload may differ from local sessions, so the extension may not be correctly parsing/accepting dropped items in Remote-SSH.

View original on GitHub ↗

6 Comments

github-actions[bot] · 8 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/17906
  2. https://github.com/anthropics/claude-code/issues/17864
  3. https://github.com/anthropics/claude-code/issues/9092

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

Coup-de-Dev · 8 months ago
I checked the suggested duplicates. #17906 is the closest, but it appears macOS-specific, while my case is Windows 11 + Cursor Remote-SSH to Debian 11. In my setup, Shift + drag shows the overlay “Drop to attach as context”, but dropping does nothing (no attachment/path, no error). This happens only over Remote-SSH; it works in a local (non-SSH) workspace on the same Windows machine. Environment: Windows 11 (client), Debian 11 (remote VPS), Cursor 2.3.30 (VS Code base 1.105.1), Claude Code extension 2.1.19. I will 👍 the related issues, but keeping this open because the Remote-SSH + Windows reproduction looks distinct and may have a different cause.
JEONG-JIWOO · 8 months ago

It still reproduces in VS Code Insider + Windows. ,

claude code version : 2.1.29

vscode: [Window Title]
Visual Studio Code - Insiders

[Main Instruction]
Visual Studio Code - Insiders

[Content]

Version: 1.109.0-insider (user setup)
Commit: 07483cec7c6bc583a449c0d444d3187f6261b911
Date: 2026-01-27T17:05:27.861Z (5 days ago)
Electron: 39.3.0
ElectronBuildId: 13168319
Chromium: 142.0.7444.265
Node.js: 22.21.1
V8: 14.2.231.22-electron.0
OS: Windows_NT x64 10.0.26200

[Copy] [OK]

github-actions[bot] · 7 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

Coup-de-Dev · 7 months ago

Root Cause Analysis

The bug is in the webview/index.js file of the Claude Code extension. The function responsible for handling dropped file URIs (minified as Jt0) hard-rejects any URI that doesn't start with file://:

// Deminified from webview/index.js (version 2.1.71)
async function Jt0($, J) {
  if (!$.trim()) return null;
  let Z = $.trim();
  if (!Z.startsWith("file://")) return null;  // <-- BUG: rejects all non-file:// URIs
  if ((Z = decodeURIComponent(Z.substring(7))), J && Z.startsWith(J)) {
    if ((Z = Z.slice(J.length), Z.startsWith("/"))) Z = Z.slice(1);
  }
  return Z;
}

When using Remote-SSH (or WSL, Dev Containers, etc.), VSCode passes URIs like:

vscode-remote://ssh-remote+hostname/path/to/file.py

This URI doesn't start with file://, so the function returns null — the drop is silently ignored. No error, no feedback to the user.

Proposed Fix

Accept vscode-remote:// URIs and extract the path using the standard URL API:

async function Jt0($, J) {
  if (!$.trim()) return null;
  let Z = $.trim();
  if (Z.startsWith("file://")) {
    Z = decodeURIComponent(Z.substring(7));
  } else if (Z.startsWith("vscode-remote://")) {
    try {
      let u = new URL(Z);
      Z = decodeURIComponent(u.pathname);
    } catch {
      return null;
    }
  } else {
    return null;
  }
  if (J && Z.startsWith(J)) {
    if ((Z = Z.slice(J.length), Z.startsWith("/"))) Z = Z.slice(1);
  }
  return Z;
}

This preserves existing file:// behavior and adds support for all vscode-remote:// schemes (Remote-SSH, WSL, Dev Containers).

Note to Anthropic: The proper fix in the source code would ideally use vscode.Uri.parse() instead of manual scheme checking, which would handle all current and future remote URI schemes automatically.

Temporary Workaround (patch script)

Until this is fixed upstream, here is a Python script that patches webview/index.js directly. It creates a backup and can be reversed with --unpatch.

Tested and confirmed working on version 2.1.71 with Cursor over Remote-SSH (Windows client → Linux server).

"""
Patches the Claude Code extension (Cursor/VSCode)
Fixes bug #21044: Drag & drop doesn't work over Remote-SSH

Usage:
    python patch_claude_extension.py                    # auto-patch
    python patch_claude_extension.py --unpatch          # restore original
    python patch_claude_extension.py <extension_path>   # patch a specific dir
"""

import os, sys, shutil, glob

def find_extension_dirs():
    home = os.path.expanduser("~")
    patterns = [
        os.path.join(home, ".cursor", "extensions", "anthropic.claude-code-*"),
        os.path.join(home, ".vscode", "extensions", "anthropic.claude-code-*"),
    ]
    dirs = []
    for p in patterns:
        dirs.extend(glob.glob(p))
    return sorted(dirs)

OLD_CODE = (
    'async function Jt0($,J)'
    '{if(!$.trim())return null;'
    'let Z=$.trim();'
    'if(!Z.startsWith("file://"))return null;'
    'if(Z=decodeURIComponent(Z.substring(7)),'
    'J&&Z.startsWith(J))'
    '{if(Z=Z.slice(J.length),'
    'Z.startsWith("/"))Z=Z.slice(1)}'
    'return Z}'
)

NEW_CODE = (
    'async function Jt0($,J)'
    '{if(!$.trim())return null;'
    'let Z=$.trim();'
    'if(Z.startsWith("file://"))'
    '{Z=decodeURIComponent(Z.substring(7))}'
    'else if(Z.startsWith("vscode-remote://"))'
    '{try{let u=new URL(Z);Z=decodeURIComponent(u.pathname)}catch{return null}}'
    'else{return null}'
    'if(J&&Z.startsWith(J))'
    '{if(Z=Z.slice(J.length),'
    'Z.startsWith("/"))Z=Z.slice(1)}'
    'return Z}'
)

def patch_file(filepath):
    with open(filepath, "r", encoding="utf-8") as f:
        content = f.read()
    if NEW_CODE in content:
        print(f"  [SKIP] Already patched: {filepath}")
        return False
    if OLD_CODE not in content:
        print(f"  [WARN] Pattern not found in: {filepath}")
        return False
    backup = filepath + ".bak"
    if not os.path.exists(backup):
        shutil.copy2(filepath, backup)
        print(f"  [BAK]  Backup created: {backup}")
    content = content.replace(OLD_CODE, NEW_CODE, 1)
    with open(filepath, "w", encoding="utf-8") as f:
        f.write(content)
    print(f"  [OK]   Patched!")
    return True

def unpatch_file(filepath):
    backup = filepath + ".bak"
    if os.path.exists(backup):
        shutil.copy2(backup, filepath)
        os.remove(backup)
        print(f"  [OK]   Restored from backup")
        return True
    print(f"  [WARN] No backup for: {filepath}")
    return False

def main():
    args = sys.argv[1:]
    is_unpatch = "--unpatch" in args
    if is_unpatch:
        args.remove("--unpatch")
    dirs = args if args else find_extension_dirs()
    if not dirs:
        print("No Claude Code extension found.")
        print("Usage: python patch_claude_extension.py [extension_path]")
        return
    print(f"Extensions found: {len(dirs)}")
    count = 0
    for d in dirs:
        print(f"\n-> {d}")
        webview_js = os.path.join(d, "webview", "index.js")
        if not os.path.isfile(webview_js):
            print(f"  [SKIP] webview/index.js not found")
            continue
        if is_unpatch:
            if unpatch_file(webview_js):
                count += 1
        else:
            if patch_file(webview_js):
                count += 1
    action = "restored" if is_unpatch else "patched"
    print(f"\n{'='*50}")
    if count:
        print(f"{count} extension(s) {action}.")
        print("Restart Cursor/VSCode to apply.")
    else:
        print("Nothing to do.")

if __name__ == "__main__":
    main()
github-actions[bot] · 7 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.