跳到主要内容

5 篇博文 含有标签「WSL2」

查看所有标签

VSCode WSL 扩展安装 Failed to fetch?vsix 下载后变 gzip 的排查与解决

· 阅读需 5 分钟

在 VSCode 大版本升级后,Claude Code 扩展卡住长时间无响应,只能卸载重装,重装时遇到 Failed to fetch、vsix 文件格式错误等一系列安装问题。

TL;DR

VSCode 升级后扩展可能卡住无响应,卸载重装时报 Failed to fetch,手动下载 vsix 又遇到格式错误。根因是 marketplace 服务器返回 gzip 压缩内容(标准 HTTP 内容协商),curl -L 跟随重定向时未自动解压,导致保存的文件是 gzip 格式而非 zip 格式。解法:手动 curl 下载 vsix → gunzip 解压 → code --install-extension 安装。

问题现象

升级 VSCode 后出现以下异常:

  1. Claude Code 对话长时间无响应,提示 Manifesting... 卡住
  2. 点击扩展图标半天无对话框窗口
  3. 只能卸载后重装,重装时各种方式报错

卸载后尝试重装,所有安装方式都失败:

# VSCode UI 安装 → Failed to fetch
# 命令行安装 → 同样失败
code --install-extension anthropic.claude-code
# 安装扩展时出错: Failed to fetch

根因

三个因素叠加导致本次异常:

VSCode 升级触发 WSL 扩展宿主重新初始化。 Claude Code 这类扩展必须在 WSL 侧安装才能运行(被定义为远程扩展主机运行),大版本升级后扩展可能卡住无响应,需要卸载重装。

VSCode 扩展安装走内部网络栈,不读 WSL 的 http_proxy 即使 WSL 中配置了代理(如 http://172.30.0.1:7897),VSCode 的扩展下载流程使用自己的网络通道,不受系统代理变量影响。

Marketplace 服务器返回 gzip 压缩内容,curl -L 未自动解压。 marketplace API 端点在 HTTP 内容协商中返回 gzip 压缩响应,这是标准行为。curl -L 跟随重定向时,在某些情况下不会自动解压 Content-Encoding: gzip,导致保存的文件是 gzip 格式而非预期的 zip 格式。用 file 命令检查会看到 gzip compressed data 而非 Zip archive data

解决方案

1. 手动下载 vsix

在 WSL 终端中用 curl 下载:

curl -L "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/anthropic/vsextensions/claude-code/latest/vspackage" \
-o ~/claude-code.vsix

2. 检查文件格式

file ~/claude-code.vsix

输出 gzip compressed data 说明文件是 gzip 格式,需要解压。输出 Zip archive data 则是原始格式,跳过下一步。

3. 解压还原 zip 格式

gunzip -c ~/claude-code.vsix > ~/claude-code-real.vsix

确认格式正确:

file ~/claude-code-real.vsix
# 输出:Zip archive data, at least v2.0 to extract

4. 确认版本(可选)

unzip -p ~/claude-code-real.vsix extension/package.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['version'])"

5. 安装

code --install-extension ~/claude-code-real.vsix

安装完成后重新加载 VSCode 窗口(Ctrl+Shift+PReload Window),扩展即可恢复正常。

WSL2 环境下这类环境问题不少见——如果你也遇到过 Docker Desktop host 网络模式下容器端口从宿主机访问不到 的问题,同样跟 WSL2 的特殊架构有关。

注意事项

  • 此解法适用于所有通过 marketplace 下载 vsix 文件变成 gzip 格式的情况,不限于 Claude Code 扩展
  • 手动安装的扩展不会自动更新,后续 VSCode 升级如果正常走更新通道则不需要再手动操作
  • 下载时可尝试加 --compressed 参数让 curl 自动处理 gzip 解压,若无效再走手动 gunzip 流程

常见问题

VSCode 扩展安装报 Failed to fetch 是代理的问题吗?

不一定是代理。marketplace 服务器本身会返回 gzip 压缩内容(标准 HTTP 内容协商),curl -L 在跟随重定向时可能不会自动解压,导致保存的文件是 gzip 格式。手动下载并 gunzip 解压后安装即可。

VSCode 升级后 WSL 扩展卡住无响应怎么办?

VSCode 大版本升级会重新初始化 WSL 远程扩展宿主,扩展可能卡住无响应。卸载后手动下载 vsix 文件,用 code --install-extension 安装即可恢复。

vsix 安装报 not a zip file 怎么办?

文件是 gzip 格式而非 zip 格式。marketplace 服务器返回 gzip 压缩内容,curl -L 未自动解压。用 gunzip -c file.vsix.gz > file.vsix 解压后再安装,用 file 命令可以确认格式。

CCLEE

独立开发者,24年电商行业实战经验,专注将AI能力落地于真实商业场景。

合作咨询

Node.js fetch 代理不生效?undici 不读 http_proxy 环境变量

· 阅读需 4 分钟

在 WSL2 环境下设置了 https_proxy 环境变量,Node.js 的 fetch() 仍然直连外网超时。

在为客户构建 AI 电商工具时遇到此问题,记录根因与解法。

TL;DR

Node.js 22+ 内置的 fetch() 基于 undici 实现,设计上不读取 http_proxy/https_proxy 环境变量。解决方案:安装 node-fetch@3 + https-proxy-agent,创建带代理配置的 fetch 实例,生产环境无代理时自动直连。

问题现象

WSL2 环境下,https_proxy 已正确设置,curl 能正常访问外网:

echo $https_proxy
# http://172.30.224.1:7897

curl -I https://httpbin.org/ip
# HTTP/1.1 200 OK

但 Node.js 的 fetch() 直接超时:

await fetch('https://httpbin.org/ip');
// FetchError: fetch failed
// cause: TimeoutError: Headers Timeout Error

如果你同时遇到 WSL2 代理完全不通(连 curl 也不行),先排查防火墙问题。

根因

Node.js v22+ 的全局 fetch() 由内置 undici 7.x 提供。undici 从设计上就不读取 http_proxy/https_proxy 环境变量——这是有意为之,不是 bug。

对比不同 HTTP 客户端的代理行为:

客户端读取环境变量走代理
curl自动读取 https_proxy
Node.js http/https 模块不读取
axios / node-fetch@3读取 https_proxy
Node.js 内置 fetch()(undici)不读取

这导致在必须通过代理才能访问外网的环境(WSL2、企业内网)下,fetch() 直连超时。

解决方案

安装 node-fetch@3https-proxy-agent

npm install node-fetch@3 https-proxy-agent

创建一个自动感知代理的 fetch 实例:

import fetch from 'node-fetch';
import { HttpsProxyAgent } from 'https-proxy-agent';

const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
const agent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : undefined;

export async function fetchWithProxy(url, options = {}) {
return fetch(url, { ...options, agent });
}

使用方式和原生 fetch() 几乎一致:

// 替换前
const res = await fetch('https://httpbin.org/ip');

// 替换后
const res = await fetchWithProxy('https://httpbin.org/ip');

为什么用 node-fetch 而不是 undici 的 ProxyAgent?

Node.js v24 内置 undici 7.x,但 npm 上的 [email protected]ProxyAgent 与内置版本不兼容:

import { ProxyAgent, setGlobalDispatcher } from 'undici';

// Node v24 下报错:UND_ERR_INVALID_ARG
// npm undici@8 的 ProxyAgent 与内置 undici@7 的 setGlobalDispatcher 不兼容
setGlobalDispatcher(new ProxyAgent(proxyUrl));

node-fetch@3 + https-proxy-agent 与 Node 版本无关,不存在兼容性问题。生产环境无代理时 agentundefined,自动直连。

注意事项

  • 不要尝试用 setGlobalDispatcher 覆盖全局 fetch——在 tsx watch 热重载环境下修改不会传播到工作模块
  • npm [email protected]FormData 类型与全局 FormData 不兼容,混用会导致 TypeScript 编译报错
  • node-fetch@3 是 ESM-only 包,import 导入即可,不支持 require()

常见问题

为什么 Node.js fetch 不读 http_proxy 环境变量?

Node.js 22+ 内置的 fetch 基于 undici 实现,undici 设计上不读取 http_proxy/https_proxy 环境变量。需要用 node-fetch 或 undici 的 ProxyAgent 手动配置代理。

Node.js fetch 如何通过代理发送请求?

安装 node-fetch@3 和 https-proxy-agent,创建带代理的 fetch 实例。生产环境无代理时自动直连,不依赖 Node 版本。

WSL2 环境下还有其他网络陷阱——Docker Desktop 的 host 模式也会让容器端口在 WSL2 里访问不到,排查思路类似:先确认 curl 能否到达,再查应用层配置。

同样的 Node 版本升级还可能踩到其他坑——比如 Node 24 下 JWT 密钥格式变更,升级时建议一并检查。

遇到 Node.js 网络问题?

联系合作

Puppeteer 被反爬检测拦截?从 Chrome CDP 到 Electron 的替代方案

· 阅读需 7 分钟

在为客户构建数据采集工具时遇到此问题,记录从 Puppeteer 到 Electron 的完整排查过程。

TL;DR

Puppeteer stealth plugin 无法绕过高级验证码反爬系统。切换到 Chrome CDP 远程调试方案后,又遇到 WSL2 网络隔离和 Chrome 单实例限制两个坑。最终用 Electron BrowserWindow 加载目标网站,用户手动登录后通过 session.cookies.get() 自动提取 Cookie,彻底解决了反爬检测和跨平台问题。

场景一:Puppeteer 被 Anti-Bot 拦截

问题现象

使用 puppeteer-extra + puppeteer-extra-plugin-stealth 自动登录目标网站,浏览器启动后立即触发验证码拦截。即使通过了验证码,后续页面也会再次检测到自动化环境并强制退出。

import puppeteer from 'puppeteer-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';

puppeteer.use(StealthPlugin());

const browser = await puppeteer.launch({
headless: false,
args: [
'--disable-blink-features=AutomationControlled',
'--no-sandbox',
'--disable-infobars',
],
});

const page = await browser.newPage();
await page.goto('https://target-site.com/login');
// 验证码系统检测到自动化环境,页面被拦截

根因

高级反爬验证码系统不只检查 navigator.webdriver 等基础指纹。它通过多个维度判断自动化环境:Chromium 编译特征、Canvas/WebGL 渲染差异、鼠标轨迹模式、甚至 DevTools Protocol 调用栈。stealth plugin 能修复已知的指纹泄露点,但无法消除 Puppeteer Chromium 与正常 Chrome 的底层差异。

经过 8 轮排查(移除超时、监听断开事件、排查环境差异、stealth plugin 补全等),确认无论怎么配置都无法绕过。

解法

放弃 Puppeteer 自动登录,改用 Chrome DevTools Protocol (CDP) 连接用户真实浏览器提取 Cookie。

场景二:WSL2 连不上 Windows Chrome CDP

问题现象

在 WSL2 中运行 Node.js 脚本,通过 chrome-remote-interface 连接 Windows Chrome 的调试端口,连接超时:

import CDP from 'chrome-remote-interface';

const client = await CDP({
host: 'localhost',
port: 9222,
});
// Error: connect ECONNREFUSED 127.0.0.1:9222

Windows 端启动 Chrome 的命令:

chrome.exe --remote-debugging-port=9222

在 Windows PowerShell 中 curl localhost:9222/json 正常返回,但 WSL2 内无法连接。

根因

Chrome --remote-debugging-port=9222 默认绑定 127.0.0.1,即 Windows 的本地回环地址。WSL2 和 Windows 有各自独立的网络栈——WSL2 内的 localhost 指向 Linux 的回环地址,不是 Windows 的。所以从 WSL2 访问 localhost:9222 实际访问的是 Linux 的 9222 端口,而非 Windows Chrome。

解法

启动 Chrome 时加 --remote-debugging-address=0.0.0.0,让 CDP 监听所有网卡:

chrome.exe --remote-debugging-port=9222 --remote-debugging-address=0.0.0.0

或者用 Windows netsh 配置端口转发:

netsh interface portproxy add v4tov4 listenport=9222 listenaddress=0.0.0.0 connectport=9222 connectaddress=127.0.0.1

注意事项

--remote-debugging-address=0.0.0.0 会将 CDP 端口暴露给局域网,存在安全风险。仅在内网开发环境使用,生产环境务必配合防火墙规则限制访问来源。

场景三:Chrome 忽略 --remote-debugging-port

问题现象

Chrome 已经在运行,带 --remote-debugging-port=9222 参数重新启动,参数被静默忽略。Chrome 只是在已有窗口中打开新标签页,CDP 端口没有开启:

# Chrome 已在运行
chrome.exe --remote-debugging-port=9222
# 没有报错,但 9222 端口并未监听

根因

Chrome 设计为单实例应用。检测到已有 Chrome 进程时,新启动的 Chrome 会将启动参数中的 URL 转发给已有进程,然后自行退出。--remote-debugging-port 等参数只在进程首次创建时生效,已有进程不会动态加载。

解法

先关闭所有 Chrome 进程,再带参数重启:

# Windows
taskkill /F /IM chrome.exe
chrome.exe --remote-debugging-port=9222

# macOS
pkill -f "Google Chrome"
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222

也可以用 --user-data-dir 指定独立配置目录,避免与日常使用的 Chrome 冲突:

chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\chrome-debug-profile"

三个场景的踩坑说明了一个事实:依赖外部 Chrome 进程做 Cookie 提取,在 WSL2 + 反爬检测 + 进程管理的组合下太脆弱。最终方案是用 Electron 的 BrowserWindow 替代外部 Chrome

为什么 Electron 有效

  1. 不触发反爬检测:Electron 内置的 Chromium 与 Chrome 共享渲染引擎,反爬系统不将其标记为自动化环境
  2. 无外部 Chrome 依赖:不需要管理 Chrome 进程、CDP 端口、profile 目录
  3. 无 WSL2 网络问题:Electron 直接运行在目标 OS 上,不存在跨系统网络隔离
  4. 无单实例冲突:Electron 创建独立的 BrowserWindow,不与用户日常 Chrome 冲突

完整实现

打开登录窗口并轮询 Cookie:

import { BrowserWindow } from 'electron';

let cookieWindow = null;

function openLoginWindow() {
cookieWindow = new BrowserWindow({
width: 1000,
height: 700,
title: '登录目标平台',
webPreferences: {
// 关键:使用独立 session,不影响主窗口
partition: 'cookie-login',
contextIsolation: true,
nodeIntegration: false,
},
});

cookieWindow.loadURL('https://target-site.com/login');

// 轮询检测目标 Cookie
const interval = setInterval(async () => {
const cookies = await cookieWindow.webContents.session.cookies.get({
domain: '.target-site.com',
});

const sessionCookie = cookies.find((c) => c.name === 'session_token');
if (sessionCookie) {
clearInterval(interval);

// 拼接完整 Cookie 字符串
const cookieStr = cookies
.map((c) => c.name + '=' + c.value)
.join('; ');

// 写入环境变量
process.env.SESSION_COOKIE = cookieStr;
updateEnvFile('SESSION_COOKIE', cookieStr);

cookieWindow.close();
}
}, 2000);

cookieWindow.on('closed', () => {
clearInterval(interval);
cookieWindow = null;
});
}

preload 脚本暴露 IPC 接口:

import { contextBridge, ipcRenderer } from 'electron';

contextBridge.exposeInMainWorld('electronAPI', {
extractCookie: () => ipcRenderer.invoke('extract-cookie'),
onCookieExtracted: (callback) =>
ipcRenderer.on('cookie-extracted', (_, data) => callback(data)),
});

渲染进程调用:

// 检测是否在 Electron 环境
if (window.electronAPI) {
document.getElementById('extractBtn').addEventListener('click', () => {
window.electronAPI.extractCookie();
});

window.electronAPI.onCookieExtracted((data) => {
console.log('Cookie 已提取:', data);
});
}

注意事项

  • partition: 'cookie-login' 创建隔离 session,登录窗口的 Cookie 不会污染主窗口。如果需要共享登录状态,去掉 partition 参数或使用相同 partition 名
  • 轮询间隔 2 秒是平衡体验和性能的经验值,不要用 while + await 替代 setInterval,那会阻塞渲染进程
  • session.cookies.get() 只能获取当前 session 的 Cookie,无法跨 partition 读取

常见问题

Puppeteer stealth plugin 仍然被反爬检测到怎么办?

stealth plugin 只能绕过基础指纹检测(如 navigator.webdriver),高级反爬系统通过浏览器行为和底层 API 特征识别自动化环境。检测维度包括 Chromium 编译特征、Canvas 渲染差异、鼠标轨迹模式等,stealth plugin 无法完全覆盖。改用 Electron BrowserWindow 加载目标网站,用户手动登录后通过 session.cookies.get() 提取 Cookie 即可。

Chrome --remote-debugging-port 为什么不生效?

Chrome 采用单进程架构,已有 Chrome 进程运行时 --remote-debugging-port 参数会被静默忽略。新启动的 Chrome 只会在现有实例中打开新标签页,调试端口不会开启。需要先用 taskkill /F /IM chrome.exe(Windows)或 pkill -f "Google Chrome"(macOS)关闭所有 Chrome 进程,再带参数重新启动。也可以用 --user-data-dir 指定独立配置目录避免冲突。

WSL2 怎么连接 Windows 上 Chrome 的调试端口?

Chrome 的 --remote-debugging-port 默认绑定 Windows 的 127.0.0.1,而 WSL2 有独立的网络栈,localhost 指向 Linux 回环地址而非 Windows。两种解法:一是启动 Chrome 时加 --remote-debugging-address=0.0.0.0 让 CDP 监听所有网卡;二是在 Windows 上用 netsh interface portproxy 配置端口转发,将 WSL2 的请求转发到 Windows 的 CDP 端口。


需要数据采集或自动化工具开发?

联系我们

容器端口在 WSL2 里访问不到?Docker Desktop 的 network_mode:host 陷阱

· 阅读需 4 分钟

在为客户搭建 AI 数据分析平台(Airflow + PostgreSQL)的本地开发环境时遇到此问题,记录根因与解法。

TL;DR

Docker Desktop for Windows (WSL2 backend) 下,network_mode: host 的容器端口无法从 WSL2 宿主机访问。容器内 ss 显示端口在监听,但宿主机 curl localhost:PORT connection refused。原因是 host 模式共享的是 Docker 内部工具 VM 的网络,不是 WSL2 的网络。解法:override 文件中用 network_mode: !reset 移除 host 模式,改用 bridge + external network + 端口映射。

修复 WSL2 无法访问 Windows 宿主机代理 — 三个隐形坑

· 阅读需 6 分钟

在 WSL2 环境下使用 Claude Code、Roo 等 AI 编程工具时,工具需要通过 Windows 宿主机代理访问 API,却反复报 ConnectionRefused。解决防火墙问题后,又遇到两个隐藏坑。记录完整排查过程。

TL;DR

WSL2 的 vEthernet (WSL) 虚拟网卡每次启动重建,Windows 防火墙无法为其分配 Network Profile,导致所有入站规则对这个接口都不生效EnforcementStatus: NotApplicable)。不需要加规则,直接对接口禁用防火墙即可:

# 在 Windows PowerShell (管理员) 中执行
Set-NetFirewallProfile -DisabledInterfaceAliases "vEthernet (WSL)"

此外,修复防火墙后,还可能遇到两个额外坑

  1. 动态 IP 问题:宿主机 IP 在 WSL/Windows 重启后变化
  2. 配置缓存问题~/.claude.json~/.claude/settings.json 里缓存了旧 API key 导致认证冲突