What happened
On Windows the session-start hook always injects this into the agent's context:
IMPORTANT: The Vercel CLI is not installed.
Strongly recommend the user install it with npm i -g vercel to unlock agentic features like vercel env pull, vercel deploy, and vercel logs.
The CLI is installed and authenticated:
$ vercel --version
Vercel CLI 56.5.0
$ vercel whoami
<my username>
The practical cost is not the wrong line, it's what the agent does with it: it takes the statement at face value and avoids vercel ls / vercel inspect for the rest of the session, so it can't check its own deployments.
Environment
- Plugin:
vercel 0.48.0 (installed from anthropics/claude-plugins-official)
- OS: Windows 11
- Node: v24.13.0
vercel installed globally with npm, so it resolves to %APPDATA%\npm\vercel.cmd
Cause
In hooks/src/session-start-profiler.mts (and identically in the compiled hooks/session-start-profiler.mjs that actually runs):
checkVercelCli() (~line 416) resolves the binary correctly. resolveBinaryFromPath does append the Windows executable extensions and finds vercel.cmd, so this part is not the bug.
- It then calls
execFileSync(vercelBinary, ["--version"]) at ~line 425 without shell: true.
- Since Node 18.20 / 20.12 (the CVE-2024-27980 hardening), spawning a
.cmd or .bat without a shell throws EINVAL.
- The
catch at ~line 433 swallows it and returns { installed: false } — which is what produces the message.
So the hook finds the CLI and then concludes it does not exist.
Minimal repro
node -e "const{execFileSync}=require('child_process');const p=require('path').join(process.env.APPDATA,'npm','vercel.cmd');try{execFileSync(p,['--version'],{stdio:['ignore','pipe','ignore']});console.log('ok')}catch(e){console.log(e.code)}"
Prints EINVAL. Adding shell: true (with the path quoted) makes it print ok.
Blast radius
- The
npm view call at ~line 449 has exactly the same shape and would fail the same way, but it is unreachable: the catch on the first call returns before it.
- The "CLI is outdated (x → y)" branch lives in the
else of installed, so on Windows it can never fire. Windows users don't get a false statement there — they get no upgrade notice at all.
- Any npm-installed CLI on Windows resolves to a
.cmd shim, so the pattern isn't specific to vercel.
Suggested fix
Spawn through a shell on Windows when the resolved path ends in .cmd / .bat, or resolve to the JS entry point behind the shim instead of the shim itself. A bare shell: true needs care with argument escaping, so quoting the resolved path explicitly is probably the safer form.
What happened
On Windows the session-start hook always injects this into the agent's context:
The CLI is installed and authenticated:
The practical cost is not the wrong line, it's what the agent does with it: it takes the statement at face value and avoids
vercel ls/vercel inspectfor the rest of the session, so it can't check its own deployments.Environment
vercel0.48.0 (installed fromanthropics/claude-plugins-official)vercelinstalled globally with npm, so it resolves to%APPDATA%\npm\vercel.cmdCause
In
hooks/src/session-start-profiler.mts(and identically in the compiledhooks/session-start-profiler.mjsthat actually runs):checkVercelCli()(~line 416) resolves the binary correctly.resolveBinaryFromPathdoes append the Windows executable extensions and findsvercel.cmd, so this part is not the bug.execFileSync(vercelBinary, ["--version"])at ~line 425 withoutshell: true..cmdor.batwithout a shell throwsEINVAL.catchat ~line 433 swallows it and returns{ installed: false }— which is what produces the message.So the hook finds the CLI and then concludes it does not exist.
Minimal repro
Prints
EINVAL. Addingshell: true(with the path quoted) makes it printok.Blast radius
npm viewcall at ~line 449 has exactly the same shape and would fail the same way, but it is unreachable: thecatchon the first call returns before it.elseofinstalled, so on Windows it can never fire. Windows users don't get a false statement there — they get no upgrade notice at all..cmdshim, so the pattern isn't specific tovercel.Suggested fix
Spawn through a shell on Windows when the resolved path ends in
.cmd/.bat, or resolve to the JS entry point behind the shim instead of the shim itself. A bareshell: trueneeds care with argument escaping, so quoting the resolved path explicitly is probably the safer form.