Supermaven-nvim cannot fetch binaries on windows due to the powershell command syntax #148

Open
opened 2025-10-26 19:40:06 -04:00 by JeanNicolasdeLamballerie · 0 comments
JeanNicolasdeLamballerie commented 2025-10-26 19:40:06 -04:00 (Migrated from github.com)

Hello,
The powershell/windows part of the binary fetcher has multiple ways of failing entirely on windows, for two reasons : Loading profiles that might be incompatible with the executable, and attempting to give a command string that is not formatted properly for powershell syntax.

This is what the binary fetcher's call looks like :

    response = vim.fn.system({
      "powershell",
      "-Command",
      "Invoke-WebRequest",
      "-Uri",
      "'" .. url .. "'",
      "-UseBasicParsing",
      "|",
      "Select-Object",
      "-ExpandProperty",
      "Content",
    })

The first, small issue out of them all is that we do not check if the user is using pwsh (cross platform, up to date solution) or powershell (built-in shell interface, legacy).

It's weird that this had not happened to anyone using neovim on windows, but I assume we are a rare breed, and the install was not necessarily properly tested on a windows machine.

While pwsh is recommended over stock powershell these days (here's a bit of info about the difference between the two for a single call to an URL, it's completely fine to use the always-available built-in powershell.exe; however, there's a few things to take into account.

The first one being that configuration files for powershell.exe and pwsh.exe are referred to by the same environment variable, $PROFILE, and pwsh and powershell have different enough syntax and libraries available that a pwsh profile will almost always make powershell.exe fail to load silently. It will usually call the command eventually, but this will almost certainly always mess up the text treatment, as the return from the call will contain stderr/out from the command.

This is an extremely easy fix : Just add -NoProfile to the call, and you're good to go, as neither pwsh or powershell will try to load a profile (which would only make the call longer anyway).

However, this is only the first problem.

Whether calling pwsh or powershell, when calling vim.fn.system, the simple approach of doing "'" .. url .. "'" within the argument list will not work. The reason is that if powershell is responsible for parsing, it will ALWAYS parse symbols like & (like the ones in https://supermaven.com/api/download-path-v2?platform=windows&arch=x86_64&editor=neovim ), even inside of a basic string, as the ampersand command - it will escape from the string and try to literally interpret it as a powershell call to a binary/command called arch (and one called editor if it ever made it to that part of the string) which obviously fails the call instantly.

There's roughly two approaches possible to fix this within neovim, as far as I know :

1) Modern vim.system()

I believe this works because vim.system properly allows us to escape string (and is the reason neovim is moving to that API) because it skips the powershell parsing entirely.

    local request = vim.system({
      "powershell",
      "-NoProfile",
      "-Command",
      ("Invoke-WebRequest -Uri '%s' -UseBasicParsing  | Select-Object -ExpandProperty Content"):format(url),
    }, { text = true }, function(obj)
      vim.notify(obj.stdout)
    end)
    local out = request.wait(x, 1000)
    vim.notify(out.stdout)

Either using the callback or waiting with a timeout and using out.stdout gives the expected string.

2) Using vim.fn.system()

Otherwise, if we want to keep using vim.fn.system() for compatibility reason, we can use string formatting and isolate the call and the faulty string into a powershell script block ({}), with something like this :

    local command = string.format(
      [[%s -NoProfile -Command "& {Invoke-WebRequest -Uri '%s' -UseBasicParsing | Select-Object -ExpandProperty Content}"]],
      "powershell",
      url
    )
    vim.notify(vim.fn.system(command))

At the very least on my machine, this yields the proper download link in either cases, instead of catastrophically failing.

As a side note, it'd also be possible to do something like local powershell = vim.fn.executable("pwsh") == 1 and "pwsh" or "powershell" if you really want to support both pwsh and powershell and replace the "powershell" string by the powershell variable, but that's not really necessary either.

I'm sure there are other ways to properly escape the url string, and I haven't checked if we attempt to do any other calls to powershell elsewhere so far that might fail, but this is a basic working solution.
I can submit a PR fixing this, or extract what the error logs look like if you'd like; I've just been manually messing with the binary fetcher code in my local lazy install for now until I had the proper output.

Of course, solution 1 can be used, and solution 2 can be a fallback if the user does not have an up-to-date neovim install.

Hello, The powershell/windows part of the binary fetcher has multiple ways of failing entirely on windows, for two reasons : Loading profiles that might be incompatible with the executable, and attempting to give a command string that is not formatted properly for powershell syntax. This is what the binary fetcher's call looks like : ```lua response = vim.fn.system({ "powershell", "-Command", "Invoke-WebRequest", "-Uri", "'" .. url .. "'", "-UseBasicParsing", "|", "Select-Object", "-ExpandProperty", "Content", }) ``` The first, small issue out of them all is that we do not check if the user is using pwsh (cross platform, up to date solution) or powershell (built-in shell interface, legacy). It's weird that this had not happened to anyone using neovim on windows, but I assume we are a rare breed, and the install was not necessarily properly tested on a windows machine. While `pwsh` is recommended over stock `powershell` these days ([here's a bit of info about the difference between the two](https://learn.microsoft.com/en-us/answers/questions/3882772/powershell-pwsh) for a single call to an URL, it's completely fine to use the always-available built-in powershell.exe; however, there's a few things to take into account. The first one being that configuration files for `powershell.exe` and `pwsh.exe` are referred to by the same environment variable, `$PROFILE`, and pwsh and powershell have different enough syntax and libraries available that a pwsh profile will almost always make powershell.exe fail to load silently. It will usually call the command eventually, but this will almost certainly always mess up the text treatment, as the return from the call will contain stderr/out from the command. This is an extremely easy fix : Just add `-NoProfile` to the call, and you're good to go, as neither pwsh or powershell will try to load a profile (which would only make the call longer anyway). However, this is only the first problem. Whether calling `pwsh` or `powershell`, when calling `vim.fn.system`, the simple approach of doing `"'" .. url .. "'"` within the argument list will **not** work. The reason is that if powershell is responsible for parsing, it will ***ALWAYS*** parse symbols like `&` (like the ones in https://supermaven.com/api/download-path-v2?platform=windows&arch=x86_64&editor=neovim ), *even inside of a basic string*, as the ampersand command - it will escape from the string and try to literally interpret it as a powershell call to a binary/command called arch (and one called editor if it ever made it to that part of the string) which obviously fails the call instantly. There's roughly two approaches possible to fix this within neovim, as far as I know : ## 1) Modern vim.system() I believe this works because vim.system properly allows us to escape string (and is the reason neovim is moving to that API) because it skips the powershell parsing entirely. ```lua local request = vim.system({ "powershell", "-NoProfile", "-Command", ("Invoke-WebRequest -Uri '%s' -UseBasicParsing | Select-Object -ExpandProperty Content"):format(url), }, { text = true }, function(obj) vim.notify(obj.stdout) end) local out = request.wait(x, 1000) vim.notify(out.stdout) ``` Either using the callback or waiting with a timeout and using out.stdout gives the expected string. ## 2) Using vim.fn.system() Otherwise, if we want to keep using vim.fn.system() for compatibility reason, we can use string formatting and isolate the call and the faulty string into a powershell script block ({}), with something like this : ```lua local command = string.format( [[%s -NoProfile -Command "& {Invoke-WebRequest -Uri '%s' -UseBasicParsing | Select-Object -ExpandProperty Content}"]], "powershell", url ) vim.notify(vim.fn.system(command)) ``` At the very least on my machine, this yields the proper download link in either cases, instead of catastrophically failing. As a side note, it'd also be possible to do something like `local powershell = vim.fn.executable("pwsh") == 1 and "pwsh" or "powershell"` if you really want to support both pwsh and powershell and replace the "powershell" string by the powershell variable, but that's not really necessary either. I'm sure there are other ways to properly escape the url string, and I haven't checked if we attempt to do any other calls to powershell elsewhere so far that might fail, but this is a basic working solution. I can submit a PR fixing this, or extract what the error logs look like if you'd like; I've just been manually messing with the binary fetcher code in my local lazy install for now until I had the proper output. Of course, solution 1 can be used, and solution 2 can be a fallback if the user does not have an up-to-date neovim install.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
supermaven-inc/supermaven-nvim#148
No description provided.