vscode-remote-release icon indicating copy to clipboard operation
vscode-remote-release copied to clipboard

code command not working in an external terminal or tmux

Open renkun-ken opened this issue 5 years ago • 20 comments

  • VSCode Version: 1.44.1
  • Local OS Version: Ubuntu 20.04
  • Remote OS Version: Ubuntu 18.04
  • Remote Extension/Connection Type: SSH

Steps to Reproduce:

  1. Connect to a remote server via Remote SSH
  2. Open the terminal pane and start a tmux server
  3. Run code and it says Command is only available in WSL or inside a Visual Studio Code terminal.

Does this issue occur when you try this locally?: No Does this issue occur when you try this locally and all extensions are disabled?: Yes

renkun-ken avatar Apr 15 '20 05:04 renkun-ken

I have found that the terminal work if from the terminal execute the next command for open vscode, you could try with code --verbose

IngJulian avatar Jun 11 '20 03:06 IngJulian

➜  qr_hfe git:(master) code -s
Unable to connect to VS Code server.
Error in request
➜  qr_hfe git:(master) code -v
1.45.1
5763d909d5f12fe19f215cbfdd29a91c0fa9208a
x64
➜  qr_hfe git:(master) code --verbose
Ignoring option verbose: not supported for code.
At least one file or folder must be provided.

renkun-ken avatar Jun 11 '20 03:06 renkun-ken

I use the following workaround

cat /usr/bin/vscode_fix_path
#!/bin/bash

OLD_CSUM=`echo $PATH | grep -oP "(?<=\/home\/$USER\/.vscode-server-insiders\/bin\/).*?(?=\/bin)" | head -1`
NEW_CSUM=`ls -tr /home/$USER/.vscode-server-insiders/bin/ | tail -n 1`

export PATH=`echo $PATH | sed "s/$OLD_CSUM/$NEW_CSUM/g"`
export GIT_ASKPASS=`echo $GIT_ASKPASS | sed "s/$OLD_CSUM/$NEW_CSUM/g"`
export VSCODE_GIT_ASKPASS_MAIN=`echo $VSCODE_GIT_ASKPASS_MAIN | sed "s/$OLD_CSUM/$NEW_CSUM/g"`
export VSCODE_GIT_ASKPASS_NODE=`echo $VSCODE_GIT_ASKPASS_NODE | sed "s/$OLD_CSUM/$NEW_CSUM/g"`
export VSCODE_IPC_HOOK_CLI=`ls -tr /tmp/vscode-ipc-* | tail -n 1`
cat /usr/bin/vscode_shell   
#!/bin/zsh

tmux new-session -A -D -s <SESSION>
tmux list-windows -t <SESSION>  | cut -d: -f1|xargs -I{} tmux send-keys -t $session:{} ENTER "source /usr/bin/vscode_fix_path" ENTER
return 0

And in remote ssh settings "terminal.integrated.shell.linux": "/usr/bin/vscode_shell",

PavanNikhilesh avatar Jun 11 '20 23:06 PavanNikhilesh

I have the same problem but with Extension/Connection Type: Containers. I'm happy to open up a different issue if anyone feels it is unrelated. I imagine it's the same root problem, though.

benjaminwood avatar Jul 15 '20 17:07 benjaminwood

I had a similar problem with remote WSL (code either wouldn't work or would open in a new window) which is also fixed by resetting the environment variables. I found that in some cases @PavanNikhilesh's script didn't work due to it finding the wrong files but I think I found a pretty simple solution for updating the environment variables.

In .tmux.conf:

# update VSCODE variables from integrated terminal so that `code` command opens in current window
set-option -ga update-environment ' VSCODE_GIT_ASKPASS_NODE VSCODE_GIT_ASKPASS_MAIN VSCODE_IPC_HOOK_CLI PATH GIT_ASKPASS'

vscode_fix_path.sh

#!/bin/bash

export "`tmux showenv PATH`"
export "`tmux showenv GIT_ASKPASS`"
export "`tmux showenv VSCODE_GIT_ASKPASS_MAIN`"
export "`tmux showenv VSCODE_GIT_ASKPASS_NODE`"
export "`tmux showenv VSCODE_IPC_HOOK_CLI`"

update-environment will make sure tmux has the right environment when making new windows/panes and then we can use the previous idea of having a script update the already running shells.

This is a pretty common problem for various SSH variables as well so there's a bit written about it.

I just run the script myself since I have persistent applications and it's not too hard to fix the shells when I notice a problem.

melink14 avatar Aug 28 '20 01:08 melink14

I came up with a reliable workaround:

  1. In the login shell (i.e. not within tmux), run nc localhost 9876 -lk | while read filename; do code "$filename"; done &

  2. In any of your tmux windows, you can run echo filename | nc localhost 9876

You can streamline this by adding #1 to your bashrc, and creating an alias for #2.

The biggest caveat is that the filename needs to be relative to your login shell's current directory.

zbs avatar Dec 30 '20 03:12 zbs

I just ran into this myself. There's probably no way for this to be fixed automatically by vscode but maybe this caveat could be documented somewhere alongside with a suggested fix similar to what @melink14 posted above? It looks like the cleanest solution so far.

yeroc avatar Mar 09 '21 19:03 yeroc

Any updates on this? I still have the need to call code filename from tmux in my current workflow.

zbs avatar Dec 08 '21 19:12 zbs

I found the most convenient solution! Both and zsh can work. Add the following content to ~/.bashrc or ~/.zshrc

socket=$(ls -1t /run/user/$UID/vscode-ipc-*.sock 2> /dev/null | head -1)
export VSCODE_IPC_HOOK_CLI=${socket}

Reference: https://stackoverflow.com/questions/62201080/is-it-possible-to-use-the-code-command-in-sshed-terminal-to-open-vs-code-on-l https://gist.github.com/ianloic/ddd8105da589d10760638af6f5306ae7

BaldStrong avatar Nov 01 '22 09:11 BaldStrong

Here's a funny screenshot where I have two tmux panes side by side, with identical environment variables. The left one cannot call code, while the right one can. The left pane was created in an earlier Remote SSH session, while the right is newer and created in the current Remote SSH session.

image

Here is a list of the environment variables that differ between the left and right panes. Nothing seems relevant to code:

[2:zeus:~]% diff left.env right.env | grep -Ee '^(<|>)' | cut -d= -f 1 | cut -d" " -f2 | sort -u    
'$'
LINENO
MATCH
MBEGIN
MEND
MENUSELECTOLDPWD
pipestatus
RANDOM
SECONDS
sz
TMUX_PANE
TTY
TTYIDLE
ZLE_LINE_ABORTED

I doesn't seem like code is deciding it is unavailable based on the environment, it must be getting information from somewhere else, like xterm.js or some shell integration escape codes (although I have shell integration disabled):

    "terminal.integrated.environmentChangesIndicator": "on",
    "terminal.integrated.env.linux": {},
    "terminal.integrated.environmentChangesRelaunch": false,
    "terminal.integrated.inheritEnv": false,
    "terminal.integrated.enablePersistentSessions": false,
    "terminal.integrated.shellIntegration.enabled": false,

Any ideas on what could be going on or possible fixes are welcome.

cunha avatar Mar 07 '23 21:03 cunha

I hope this will work and is simplest:

alias code="${VSCODE_GIT_ASKPASS_NODE%/*}/bin/remote-cli/code"

sh1vy avatar Mar 15 '24 06:03 sh1vy

I hope this will work and is simplest:

alias code="${VSCODE_GIT_ASKPASS_NODE%/*}/bin/remote-cli/code"

This saves my day. Thanks a lot.

need47 avatar Mar 16 '24 00:03 need47

The previous solutions don't work for me. The directory /run/user/$UID does not exist, and ${VSCODE_GIT_ASKPASS_NODE%/*}/bin/remote-cli/code is not found. But I got the key to the solution, which is to set the correct value to env VSCODE_IPC_HOOK_CLI.

Paste code below in your .bashrc, then open a new terminal within your current vscode, execute cmd mcw TMUX_SESSION_NAME, then the code in tmux works fine~

function make_code_work() {
    # make code work in tmux
    if [ -z "$1" ]; then
        echo "Provide a tmux session name"
        return 1
    fi
    SESSION_NAME=$1
    t=$(set | grep VSCODE_IPC_HOOK_CLI= | head -n 1 | cut -d'=' -f2)
    if [ -z "$t" ]; then
        echo "VSCODE_IPC_HOOK_CLI is not set. Are you running this in a VS Code terminal?"
        return 1
    fi
    echo "Socket to your current vscode: $t"
    cmd="export VSCODE_IPC_HOOK_CLI=$t"
    echo cmd=$cmd
    tmux list-windows -t $SESSION_NAME -F '#{window_id}' | while read WINDOW_ID; do
        tmux list-panes -t $WINDOW_ID -F '#{pane_id}' | while read PANE_ID; do
            echo "Executing command in window $WINDOW_ID, pane $PANE_ID"
            tmux send-keys -t $PANE_ID "$cmd" C-m
        done
    done
}
alias mcw='make_code_work'

zzqq2199 avatar May 22 '24 08:05 zzqq2199

https://github.com/microsoft/vscode-remote-release/issues/2763#issuecomment-2124135704

Building on this answer, I added an update to the tmux environment for the session (for new panes that are created), and I added checks to only run outside of tmux in the vscode terminal (where the variable should be correct) and only when the pane has one process (to avoid setting the variable on remote connections, probably a better way as this will skip panes running jobs)

function tmux-session-fix() {
  local session_id=$1
  local socket_name="$2"

  if [ -z "${session_id}" ]; then
    echo "Usage: tmux-session-fix <session_id> ?<socket_name>"
    return 1
  fi

  if [ -n "${socket_name}" ]; then
    local socket_path="/tmp/tmux-${UID}/${socket_name}"
  else
    local socket_path="/tmp/tmux-${UID}/default"
  fi

  if [ "${TERM_PROGRAM}" != "vscode" ]; then
    echo "This command is only meant to be run outside of tmux, in the VS Code shell. Returning."
    return 1
  fi

  local vscode_ipc_hook_cli; vscode_ipc_hook_cli=$(set | grep VSCODE_IPC_HOOK_CLI= | head -n1 | cut -d'=' -f2)
  if [ -z "${vscode_ipc_hook_cli}" ]; then
    echo "VSCODE_IPC_HOOK_CLI is not set. Are you running this in a VS Code terminal? Returning."
    return 1
  fi

  cmd_vscode_ipc_hook_cli="export VSCODE_IPC_HOOK_CLI=${vscode_ipc_hook_cli}"
  echo "Command: ${cmd_vscode_ipc_hook_cli}"

  # Set tmux environment variable for new panes
  tmux -S "${socket_path}" set-environment -t "${session_id}" VSCODE_IPC_HOOK_CLI "${vscode_ipc_hook_cli}"

  # Update all panes that only have one process running
  tmux -S "${socket_path}" list-windows -t="${session_id}" -F "#{window_id}" \
      | while read -r window_id; do
    tmux -S "${socket_path}" list-panes -t="${window_id}" -F '#{pane_id} #{pane_tty}' \
        | while read -r pane_id pane_tty; do
      local pid_count; pid_count=$(ps -t "${pane_tty}" -o pid= | wc -l)
      if [ "${pid_count}" -gt 1 ]; then
        echo "Pane ${pane_id} has more than one process running. Skipping"
        continue
      else
        echo "Executing command in socket: ${socket_path}, session: ${session_id}, window: ${window_id}, pane: ${pane_id}"
        tmux -S "${socket_path}" send-keys -t "${pane_id}" "${cmd_vscode_ipc_hook_cli}" C-m
      fi
    done
  done
}

m1tttt4 avatar Jul 14 '24 02:07 m1tttt4

This is my second time investigating this problem; the first time I was put off by both a) complexity in getting a correct value for VSCODE_IPC_HOOK_CLI, b) really bad complexity trying to change the environment for existing and new tmux panes. I didn't solve it as a result, and coming back to it, I see more of the same. So I said "to hell with it" and went caveman on the problem by just adding this to my zshrc:

if [[ $TERM_PROGRAM == "vscode" ]]; then
      echo $VSCODE_IPC_HOOK_CLI >! ${ZDOTDIR}/vscode_ipc.txt
fi

alias code='VSCODE_IPC_HOOK_CLI=$(cat $ZDOTDIR/vscode_ipc.txt) code'

Yes, you have to use a file. But this solution is dead simple, easy to understand and debug, and seems to always work so far.

Edit: I will mention, if you set your vscode terminal profile as launching tmux directly, then I'm not 100% sure it will work, or it may depend which exact startup file you put this in, etc. For me personally, I prefer to have vscode start a zsh terminal, and then I run tmux myself - just to have control of whether I'm attaching, or spawning a new client, etc - I didn't really see much point in automating that. If you use that setup, then I'm pretty sure this will work well.

Edit 2: it occurred to me at some point that if you were running multiple vscode windows, you'd run into issues with this approach. I tend not to do this; if you do do this then you'll have to commit to more complex approaches, as at that point you will need to have a way to "tell" a particular tmux session which vscode GUI to be associated with; the previous two solutions seem promising for this.

quicknir avatar Aug 15 '24 20:08 quicknir

I seem to keep coming back to this problem, as I encounter small edge cases with existing solutions :-). My previous solution had some of the limitations I mentioned. I think this new solution works in all cases, including with multiple vscode windows. It's basically a combination of the idea stated by others to use tmux update-env, and my idea of changing the code command itself to "pull" the correct value (which is a lot easier than "pushing" it).

The first step is just to add this to your tmux.conf

set-option -ga update-environment VSCODE_IPC_HOOK_CLI

The second step is to add this function to your .zshrc or wherever

code() {
  local vscode_ipc=$(tmux show-env VSCODE_IPC_HOOK_CLI | cut -d '=' -f2) 2>/dev/null
  if [[ -v TMUX && vscode_ipc != "" ]]; then
    VSCODE_IPC_HOOK_CLI=$vscode_ipc command code "$@"
  else
    command code "$@"
  fi
}

The first step ensures that every time you spawn or attach a tmux session, VSCODE_IPC_HOOK_CLI's value will be copied into the tmux session's environment - note that this does not change the environment variables of existing panes! Then, instead of trying to push this variable to all the panes which is quite error prone, we just write a small function code function which grabs the value of VSCODE_IPC_HOOK_CLI from the current session, instead of using the environment variable. If the value is not available, then you are not currently attached to a vscode window. If the value is available and we're inside tmux we use that value instead of what the environment provides.

Outside of attaching to a tmux session both inside and outside vscode simultaneously, I can't think of any other situation this would break down in, and I do think this is the best solution so far, for the specific case of running tmux inside vscode.

quicknir avatar Nov 06 '24 18:11 quicknir

Can't believe how hard it is to make this work, and in the end I still have one grievance, which is that a cli.js file keeps opening on its own, since I can't get tmux to correctly communicate with VSCode on Windows through a socket.

Currently, I make it work by having this on my .tmux.conf:

# inherit environment variables from wsl
set-option -g update-environment "\
    PATH \
    WSLENV \
    DISPLAY \
    XAUTHORITY \
    SSH_AUTH_SOCK \
    DBUS_SESSION_BUS_ADDRESS \
    VSCODE_IPC_HOOK_CLI \
    VSCODE_GIT_ASKPASS_NODE \
    VSCODE_GIT_ASKPASS_MAIN \
    GIT_ASKPASS \
    LANG \
    LC_ALL"

And this on my .zshrc:

# override the "code" command to always include the --remote flag for Ubuntu
code() {
  if [[ "$1" = "." ]]; then
    # When using ".", expand it to the current directory.
    command code --remote wsl+Ubuntu "$(pwd)"
  else
    command code --remote wsl+Ubuntu "$@"
  fi
}

Then I can use code . within a tmux session, and it will open VSCode and remote connect to WSL.

bathwaterpizza avatar Feb 13 '25 13:02 bathwaterpizza

Posting my own solution to this in case it helps others. My issue was that upon re-attaching to tmux (or maybe some other trigger), my code command stopped working:

Error: connect ECONNREFUSED /run/user/79563/vscode-ipc-f69e973c-6565-42a7-a98b-975c098eea04.sock
    at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1611:16) {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '/run/user/79563/vscode-ipc-f69e973c-6565-42a7-a98b-975c098eea04.sock'
}

The issue happens because of the VSCODE_IPC_HOOK_CLI env var being set upon connecting via ssh in VSCode, but that env var not being propagated to open shells in existing tmux sessions. I'm not an expert but I think what's happening is a new socket connection is being created upon ssh to the VSCode Server, while the old socket eventually expires (or something like that). Open shells for existing sessions continue to use the expired socket causing the code command to fail. My solution was adding the following to my .bashrc file:

export_vscode_env() {
    local env_file="${1:-/tmp/vscode_env.sh}"
    
    echo "export VSCODE_IPC_HOOK_CLI=\"$VSCODE_IPC_HOOK_CLI\"" > "$env_file"
    
    echo "VSCODE_IPC_HOOK_CLI exported to $env_file"
}

# Import VSCODE_IPC_HOOK_CLI from a file
import_vscode_env() {
    local env_file="${1:-/tmp/vscode_env.sh}"
    
    if [[ -f "$env_file" ]]; then
        source "$env_file"
    else
        echo "Error: Environment file $env_file not found"
        return 1
    fi
}

tmux() {
    export_vscode_env
    command tmux "$@"
    if [ -n "$TMUX" ]; then
        tmux set-environment VSCODE_IPC_HOOK_CLI "$VSCODE_IPC_HOOK_CLI"
    fi
}

That'll set the environment variable for code anytime you call the code command (Note that setting the environment variable in for example, .tmux.conf via set-option -g update-environment only allows new sessions/windows/panes to inherit the new value. It doesn't do anything for existing sessions). That's why I overrode my code command in tmux to source the env var containing the updated VSCODE_IPC_HOOK_CLI value. Now anytime I call code it'll automatically pick up the correct value.

I really think VSCode developers should take a look at this in depth. It's a very annoying problem and not obvious to solve unless you spend a lot of time understanding what VSCode does under the hood (it took me a lot of searching to even figure out the problem was with $VSCODE_IPC_HOOK_CLI). Hope this helps anyone who was as frustrated as me trying to get this to work.

gr93 avatar Mar 27 '25 19:03 gr93

Instead of doing the env.sh file, you could just do source tmux show-environment -s I have that command running as a prexec hook. That seems to work just fine for me.

My fish hook:

function refresh_tmux_vars --on-event="fish_preexec"
  if set -q TMUX
    bass (tmux show-environment -s)
  end
end

cazador481 avatar Jun 04 '25 14:06 cazador481