Windows: SessionStart hook always reports "Vercel CLI is not installed" (two bugs in checkVercelCli)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 287
- Forks
- 58
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 17
Description
Summary
On Windows, checkVercelCli() in hooks/session-start-profiler.mjs can never detect an npm-installed Vercel CLI. Every session starts with:
IMPORTANT: The Vercel CLI is not installed.
Strongly recommend the user install it withnpm i -g vercelto unlock agentic features likevercel env pull,vercel deploy, andvercel logs.
...on a machine where the CLI is installed and authenticated.
This is worse than a cosmetic wrong message. Agents believe it. In my case an agent needed to set a production environment variable, read that line at session start, concluded the capability was unavailable, and handed the task back to the user rather than running vercel env add. The hook meant to help the agent use Vercel actively prevented it.
Plugin version 0.45.1. Windows 10, Node 22.19.0, npm-installed CLI 54.20.1.
Two independent bugs, either one fatal
1. Candidate order resolves to the one file Node cannot execute
function getBinaryPathCandidates(binaryName) {
if (process.platform !== "win32") return [binaryName];
const hasExecutableExtension = /\.[^./\]+$/.test(binaryName);
const suffixes = hasExecutableExtension ? [""] : ["", ...WINDOWS_EXECUTABLE_EXTENSIONS];
return suffixes.map((suffix) => `${binaryName}${suffix}`);
}
The bare, extensionless name is tried first. On Windows, npm installs two files into its bin directory:
C:\Users\<user>\AppData\Roaming\npm\vercel.cmd <- the Windows shim
C:\Users\<user>\AppData\Roaming\npm\vercel <- a Unix shell script, for Git Bash / MSYS
Both satisfy accessSync(path, X_OK), so resolveBinaryFromPath returns the shell script, which Node cannot spawn. execFileSync throws ENOENT, the catch returns { installed: false }, and the hook reports the CLI missing.
2. The correct file would also have failed
Node >= 18.20 / 20.12 refuses execFileSync on .cmd / .bat without shell: true — the fix for CVE-2024-27980. So the shim throws EINVAL.
Reproduced directly:
import { execFileSync } from 'node:child_process';
for (const p of [String.raw`C:\Users\Cara\AppData\Roaming\npm\vercel`,
String.raw`C:\Users\Cara\AppData\Roaming\npm\vercel.cmd`]) {
try { console.log('OK ', p, execFileSync(p, ['--version'], {encoding:'utf-8'}).trim()); }
catch (e) { console.log('THROW', p, e.code); }
}
THROW C:\Users\Cara\AppData\Roaming\npm\vercel -> ENOENT
THROW C:\Users\Cara\AppData\Roaming\npm\vercel.cmd -> EINVAL
Fixing the ordering alone is not enough; both need addressing.
Knock-on effect
checkVercelCli() resolves npm through the same helper for the npm view vercel version call, so the "your CLI is outdated" branch is dead on Windows for the same two reasons. In my case that hid a CLI five major versions behind (54.20.1 vs 59.5.0) — invisible for as long as the check kept answering "not installed".
Suggested fix
Two changes in hooks/src/session-start-profiler.mts (and the built .mjs):
Put the extensions first, the bare name last.
function getBinaryPathCandidates(binaryName) {
if (process.platform !== "win32") return [binaryName];
if (/\.[^./\]+$/.test(binaryName)) return [binaryName];
// A bare, extensionless file in a Windows PATH directory is almost always the
// Unix shell script npm writes beside its .cmd shim, and Node cannot exec it.
return [...WINDOWS_EXECUTABLE_EXTENSIONS.map((s) => `${binaryName}${s}`), binaryName];
}
Route .cmd / .bat through a shell, quoted.
function execBinarySync(binaryPath, args) {
const needsShell = process.platform === "win32" && /\.(cmd|bat)$/i.test(binaryPath);
if (needsShell) {
return execFileSync(`"${binaryPath}"`, args.map((a) => `"${a}"`), {
timeout: EXEC_SYNC_TIMEOUT_MS, encoding: "utf-8", stdio: SPAWN_STDIO, shell: true,
});
}
return execFileSync(binaryPath, args, {
timeout: EXEC_SYNC_TIMEOUT_MS, encoding: "utf-8", stdio: SPAWN_STDIO,
});
}
Then call execBinarySync at both execFileSync sites in checkVercelCli().
Verified locally
Applied to the cached 0.45.1 copy:
before: {"installed":false,"needsUpdate":false}
after : {"installed":true,"currentVersion":"54.20.1","latestVersion":"59.5.0","needsUpdate":true}
The hook then correctly printed the outdated message instead of the not installed one, and after upgrading to 59.5.0 it correctly prints nothing at all.
Detection takes ~850 ms end to end, comfortably inside the existing 3 s EXEC_SYNC_TIMEOUT_MS, so no timeout change is needed.
Happy to open a PR if that is useful.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read hooks/src/session-start-profiler.mts and the built hooks/session-start-profiler.mjs, focusing on checkVercelCli(), getBinaryPathCandidates(), and the two execution sites. Reproduce the Windows npm-installed case described in the issue, then verify the hook distinguishes installed, outdated, and missing Vercel CLI states without exceeding the existing timeout.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100