Claude Code Windows环境避坑指南

· 2026-04-13 13:59 · 5 阅读

原创 rayh4c 2026-04-13 13:59 北京

把 AI 工具链标准化成可重复执行的环境基础设施

我在 Windows 上用 Claude Code 已经有一段时间了。过程中踩过不少坑:编码乱码、LSP 插件找不到二进制、Agent Teams 在 Windows 上根本不工作、国内网络访问 API 端点不稳定……每一个单独看都不算大问题,但叠在一起,足以让一次本该顺畅的开发体验变成半天的排障马拉松。

这些问题攒多了,我就把解决方案整理成了一个项目:OhMyWinClaude。它不是一个简单的装机脚本合集,而是一套以 just 为统一入口、面向 Claude Code 的 Windows 开发环境编排方案,覆盖了从底层工具链、语言运行时、LSP 语言服务、MCP 接入,到 Hook 注入和 Shim 部署的完整链路。

这篇文章会把我在做这个项目过程中遇到的关键问题和设计决策拆开讲清楚,特别是 Hook 和 Shim 这两个在 Windows 上绕不开、但很少有人系统讲过的机制。


一、先说整体架构:以 just 为入口的任务编排

OhMyWinClaude 的核心不是某个单一脚本,而是以 just(一个类似 make 的命令运行器)作为统一入口,把一批幂等的 PowerShell 脚本编排成可安装、可卸载、可检查、可分组调用的任务系统。

你面对的不是零散的脚本文件,而是一组语义清晰的命令:

  • just install-dev:安装完整开发环境

  • just install-claude:安装 Claude Code 及相关配置

  • just status-dev:检查当前环境状态

  • just install-wsl:配置 WSL 环境

目录结构上,安装逻辑集中在 scripts/,配置模板集中在 templates/,两者显式分层。你可以只复用模板而不执行安装,也可以只跑安装而替换成自己的模板。

所有脚本都支持幂等执行,已安装的组件会自动跳过。这让它更像一套“环境编排系统”,而不是一次性安装向导。

install-dev 的编排顺序体现了我对 Claude Code 工作环境的理解:先铺底层工具链(Git、fzf、jq、ripgrep),再装语言与运行时(Rust、Python、Node.js),然后补齐面向 Claude Code 的插件与扩展能力(LSP、MCP、Playwright)。这个顺序背后的核心认知是,Claude Code 不是一个孤立的 CLI 工具,它需要运行在一个具备完整开发能力的操作面之上。


二、Windows 上最头疼的编码问题:用 PreToolUse Hook 从根源解决

在 Windows 上用 Claude Code,最先撞上的墙往往不是安装,而是编码。Windows 默认的控制台代码页是 GBK(936),而 Claude Code 通过 Git Bash 执行命令时,输出的中文内容经常变成乱码,Python 脚本的 stdout 也会因为编码不一致而报错。

手动每次 chcp 65001 太蠢了,而且 Claude Code 每次调用 Bash 工具时都是独立的命令执行,你没法在一个命令里设好编码然后指望下一个命令还生效。

我的解决方案是利用 Claude Code 的 PreToolUse Hook。这是 Claude Code 官方提供的生命周期钩子机制,在每次工具调用执行之前,你可以通过一个脚本拦截它,检查输入,甚至修改工具的输入参数。

具体来说,我写了一个 Python 脚本作为 PreToolUse Hook,它做的事情很简单但很关键:

import sys

import json

try:

    data = json.loads(sys.stdin.read())

    original_cmd = data.get("tool_input", {}).get("command""")

    prefix = (

"chcp.com 65001 > /dev/null 2>&1; "

"export LANG=zh_CN.UTF-8; "

"export LC_ALL=zh_CN.UTF-8; "

"export PYTHONUTF8=1; "

"export PYTHONIOENCODING=utf-8; "

"export LESSCHARSET=utf-8; "

    )

    sys.stdout.write(json.dumps({

"hookSpecificOutput": {

"hookEventName""PreToolUse",

"permissionDecision""allow",

"updatedInput": {

"command": prefix + original_cmd

            }

        }

    }))

except Exception:

pass

sys.exit(0)

这段代码的工作原理是:

  1. Claude Code 每次要执行 Bash 命令时,会把命令内容通过 stdin 以 JSON 格式传给 Hook 脚本

  2. 脚本读取原始命令,在前面拼接一段编码设置前缀:chcp.com 65001 切换控制台代码页到 UTF-8,同时设置 LANGLC_ALLPYTHONUTF8PYTHONIOENCODINGLESSCHARSET 等环境变量

  3. 通过 updatedInput 把修改后的命令写回 stdout,Claude Code 就会用这个新命令替代原始命令去执行

  4. 同时返回 permissionDecision: "allow",自动放行这个命令,不再弹权限确认

这里有几个设计细节值得展开:

为什么用 updatedInput 而不是 SessionStart Hook? Claude Code 确实提供了 SessionStart Hook,可以在会话启动时通过 CLAUDE_ENV_FILE 持久化环境变量。但 chcp.com 65001 不是环境变量,它是一个需要在每次命令执行时都生效的控制台状态切换。而且 Git Bash 的子进程不一定继承父进程的 chcp 状态。所以必须在每条命令前都注入一次,PreToolUse + updatedInput 是唯一可靠的方案。

为什么输出重定向到 /dev/null 因为 chcp.com 65001 会输出 “Active code page: 65001” 这样的文本,如果不抑制,这段文本会混入命令的 stdout,可能干扰 Claude Code 对输出结果的解析。

为什么同时设置这么多环境变量? 因为不同工具读不同的变量。Python 看 PYTHONUTF8 和 PYTHONIOENCODING,GNU 工具看 LANG 和 LC_ALL, less 看 LESSCHARSET。一次全设,省得后面一个一个排查。

在 settings.json 中的配置方式是这样的:

{

"hooks":{

"PreToolUse":[

{

"matcher":"Bash",

"hooks":[

{

"type":"command",

"command":"python scripts/hooks/pre-bash-utf8.py"

}

]

}

]

}

}

matcher 设为 "Bash" 意味着这个 Hook 只在 Claude Code 调用 Bash 工具时触发,不会影响 Read、Write、Edit 等其他工具。这是 Claude Code Hook 系统的一个很好的设计,你可以精确控制 Hook 的作用范围。


三、LSP 的 Shim 问题:为什么语言服务器装了却找不到

Claude Code 的插件体系支持 LSP(Language Server Protocol)集成。官方提供了 pyright-lsptypescript-lsp 等插件,但这些插件只负责描述如何连接语言服务器,不会自带语言服务器二进制。你需要自己安装 Pyright、typescript-language-server 等工具,插件只是告诉 Claude Code:“用这个命令启动语言服务器,这些文件扩展名对应这个语言。”

在 macOS 和 Linux 上,npm install -g typescript-language-server typescript 之后,typescript-language-server 命令就在 PATH 里了,一切正常。但在 Windows 上,事情没这么简单。

问题出在 npm 全局安装的包在 Windows 上的可执行文件解析机制。npm 全局安装时会在 node_modules 下放实际的 JS 文件,然后在 npm 的全局 bin 目录下生成 .cmd 批处理文件作为入口。但 Claude Code 的 LSP 插件在启动语言服务器时,期望的是一个可以直接执行的二进制文件,而不是一个 .cmd 脚本。在某些 Shell 环境下(特别是 Git Bash),.cmd 文件的解析行为和 PowerShell 下不一致,导致语言服务器启动失败。

我的解决方案是部署 Shim Exe。以 TypeScript LSP 的安装脚本为例:

# ---- 3. Deploy shim exe (node.exe + cli.mjs) ----

$nodeDir        = "D:\DevEnvs

ode"

$shimExePath    = Join-Path $nodeDir "typescript-language-server.exe"

$cliMjsPath     = Join-Path $nodeDir "node_modules\typescript-language-server\lib\cli.mjs"

if ((Test-Path (Join-Path $nodeDir "node.exe")) -and (Test-Path $cliMjsPath)) {

    try {

        Install-ShimExe -TargetExePath $shimExePath `

                        -ShimTargetPath "$nodeDir

ode.exe" `

                        -ShimArgs "$cliMjsPath"

    }

    catch {

        Write-Host "[WARN] Shim deployment failed (non-critical): $_" -ForegroundColor Yellow

    }

}

Install-ShimExe 做的事情是:生成一个真正的 .exe 文件(shim),这个 exe 启动时会自动转发调用到 node.exe,并把 cli.mjs 作为参数传入。最终效果是,当 Claude Code 的 LSP 插件执行 typescript-language-server --stdio 时,它找到的是一个真正的 .exe,这个 exe 内部转发到 node.exe node_modules/typescript-language-server/lib/cli.mjs --stdio

这个 shim 机制解决了三个问题:

  1. 跨 Shell 一致性:无论是 PowerShell、Git Bash 还是 cmd.exe,.exe 文件的行为都是一致的,不存在 .cmd 文件在不同 Shell 下的解析差异

  2. 路径可控:shim exe 放在 D:\DevEnvs ode 目录下,和 node.exe 在同一个目录,PATH 管理更简单

  3. Claude Code LSP 插件兼容:LSP 插件的 .lsp.json 配置中 command 字段直接写 typescript-language-server,不需要写复杂的启动脚本

本质上,在 Windows 上做 Claude Code 的 LSP 集成,shim 是绕不开的基础设施。


四、psmux 与 Agent Teams:Windows 上的 tmux 替代方案

Claude Code 的 Agent Teams 功能允许主 agent 把任务分派给多个 teammate agent,每个 teammate 在独立的 tmux pane 中运行,你可以实时看到每个 agent 在做什么。这是一个非常强大的功能,但它有一个前提:你得有 tmux。

在 macOS 和 Linux 上,tmux 是标配。但 Windows 上没有原生的 tmux。这就是为什么 OhMyWinClaude 会安装 psmux,一个用 Rust 写的、专门为 Windows PowerShell 设计的 tmux 兼容层。

psmux 对 Claude Code 的支持不是简单的“能分屏”,而是做了深度的适配。根据 psmux 的文档,它解决了 Claude Code 在 Windows 上的两个关键问题:

第一,Agent Teams 的功能门控。 Claude Code 的 teammate 工具集(spawnTeam、spawnTeammate)被一个环境变量 CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 门控着。没有这个变量,Claude 只有 in-process 的 Agent 工具,永远不会创建独立的 pane。psmux 在每个 pane 启动时自动设置这个变量。

第二,teammateMode配置被忽略的问题。 Claude Code 的独立二进制(Bun SFE 打包的 claude.exe)会忽略 ~/.claude/settings.json 中的 teammateMode: "tmux" 配置。psmux 的解决方案是定义一个 PowerShell wrapper function,在每次 claude 调用时自动注入 --teammate-mode tmux 参数。这就是所谓的 shim,不是修改 Claude Code 本身,而是在调用链上包一层,透明地注入所需的参数。

psmux 在每个 pane 中自动设置的环境变量:

变量

作用

TMUX/tmp/psmux-{pid}/...

让 Claude Code 检测到自己在 tmux 环境中

CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS1

启用 Agent Teams 功能门控

PSMUX_CLAUDE_TEAMMATE_MODEtmux

触发 --teammate-mode tmux 的 CLI 注入

当 Claude Code 检测到 TMUX 环境变量后,它会使用 TmuxBackend 通过 split-window 和 send-keys 来创建 teammate agent,这和它在 Linux/macOS 上使用原生 tmux 的机制完全一致。

这里有一个值得注意的细节:Claude Code 实际上有两套完全独立的 agent 系统。Teammate 系统在可见的 tmux pane 中运行,psmux 完全支持;Worktree 系统则创建独立的 git worktree 并在进程内运行 agent,对用户不可见。模型会自己选择用哪套系统。Haiku 和 Sonnet 倾向于用 Teammate,Opus 倾向于用 Worktree(因为 git 级别的隔离更安全)。如果你希望强制使用 Teammate 系统以获得可见性,可以在 CLAUDE.md 中加入相应的指令。


五、配置分层:settings.json 模板与环境变量的职责划分

Claude Code 官方提供了两条配置路径:settings.json 文件(支持 user、project、local、managed 四级作用域)和环境变量。OhMyWinClaude 把这两条路径都用上了,而且有明确的职责划分。

settings.json:收敛行为默认值

templates/claude-settings.json 模板设置了:

  • defaultShell: powershell:统一 Shell 行为

  • teammateMode: auto:Agent Teams 显示模式

  • autoUpdatesChannel: stable:锁定稳定更新通道

  • permissions:定义权限边界,哪些命令自动放行、哪些需要确认、哪些直接拒绝

  • hooks:配置 PreToolUse、SessionStart 等生命周期钩子

  • enabledPlugins:声明启用的 LSP 插件列表

这些配置的目的是把 Claude Code 的行为收敛到一个可复制的默认工作模式里。团队成员拿到同一份模板,行为就是一致的。

环境变量:隔离认证信息

scripts/set-claude-env.ps1 会把 ANTHROPIC_AUTH_TOKEN 和 ANTHROPIC_BASE_URL 写入 Windows 用户级环境变量,并同步到当前会话。这两个都是 Claude Code 官方支持的环境变量,ANTHROPIC_AUTH_TOKEN 的值会被加上 Bearer 前缀作为 Authorization header 发送,ANTHROPIC_BASE_URL 用于改写 API 端点。

为什么要把行为配置和认证配置分开? 因为 settings.json 模板是要提交到仓库、团队共享的;而 API Token 这种认证信息绝对不能出现在项目文件里。落到用户级环境变量,既能持久化,又不会泄露。

关于 ANTHROPIC_BASE_URL,我默认设成了 https://open.bigmodel.cn/api/anthropic,因为这是目前国内最好的Claude模型替代,其他都是渣渣。但用哪个端点是你自己的选择,根据你的网络条件和 API 提供商来决定就好。


六、插件与 LSP 的注册:两件事,不能混成一件

前面讲了 shim 解决的是“语言服务器二进制在 Windows 上的可执行性”问题。但光有二进制还不够,你还需要告诉 Claude Code:“这个语言服务器存在,用这个命令启动它,这些文件扩展名归它管。”

这就是 Claude Code 插件体系中 LSP 插件的职责。一个 LSP 插件的核心是 .lsp.json 文件,格式大致如下:

{

"typescript-language-server":{

"command":"typescript-language-server",

"args":["--stdio"],

"transport":"stdio",

"extensionToLanguage":{

".ts":"typescript",

".tsx":"typescriptreact",

".js":"javascript",

".jsx":"javascriptreact"

}

}

}

command 字段就是前面 shim 解决的那个问题,它需要是一个可以直接执行的命令。extensionToLanguage 告诉 Claude Code 哪些文件扩展名应该路由到这个语言服务器。

OhMyWinClaude 的做法严格遵循了这个分层:

  1. 用独立脚本安装语言服务器本体(Pyright、typescript-language-server、PowerShellEditorServices),并部署 shim exe

  2. 再通过 install-claude-plugin.ps1 向 Claude Code 注册 LSP 插件

仓库还自带了一个本地插件市场 local-dev,其中注册了 powershell-lsp 插件。这是因为 Claude Code 官方市场目前提供了 pyright-lsp 和 typescript-lsp,但没有 PowerShell 的 LSP 插件,所以我自己做了一个本地的。


七、MCP 接入:只注册端点,不托管服务

OhMyWinClaude 会把 Jupyter MCP Server 注册到 Claude Code 的用户级配置中:

claude mcp add jupyter --scope user --transport http `

    http://127.0.0.1:8888/mcp --header"Authorization: Bearer jupyter"

注意,仓库只负责注册 MCP 端点,不负责启动 Jupyter 服务本身。这是一个有意的设计,声明连接关系,但不越界管理服务生命周期。你需要自己确保 Jupyter 服务在 127.0.0.1:8888 上运行并且开启了 MCP 支持。

这种“平台接线”的思路贯穿了整个项目:OhMyWinClaude 负责把 Claude Code 和各种外部工具接起来,但每个工具自身的运行状态由你自己管理。


八、Windows 特有的工程化处理

很多跨平台工具的官方文档默认网络顺畅、Unix Shell 稳定,但 Windows 实际落地时的问题远不止“命令怎么写”。OhMyWinClaude 把这些现实约束当成一等公民来处理:

路径集中管理。 所有开发工具集中安装到 D:\DevEnvsD:\WSLD:\DevSetup 等显式路径,避免散落在 C:\Users\xxx\AppData 的各种角落里。这样做的好处是迁移和备份变得极其简单,整个开发环境就是一个目录。

国内网络适配。 支持 gh-proxy 镜像加速 GitHub 资源下载,提供离线缓存机制和 SHA256 校验,应对网络不稳定场景。首次搭建的失败率因此大幅降低。

Shell 行为统一。 模板中显式设置 defaultShellCLAUDE_CODE_GIT_BASH_PATH、以及前面详细讲过的 UTF-8 Hook,确保无论在 PowerShell、Git Bash 还是 cmd.exe 下,Claude Code 的行为都是一致的。

环境即代码。 用 just + PowerShell 把安装、卸载、检查、配置全部任务化。just status-dev 一条命令就能看到当前环境的完整状态,哪些装了、哪些没装、版本号是多少,一目了然。


九、一些需要注意的边界

任何工具都有边界,OhMyWinClaude 也不例外,有几个点我自己也在持续迭代:

部分环境变量属于实验性质。 模板中的 CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS 是 Agent Teams 的功能门控变量,目前还带着 EXPERIMENTAL 前缀,说明 Anthropic 还没有把它标记为稳定接口。CLAUDE_CODE_USE_POWERSHELL_TOOLENABLE_LSP_TOOL 等变量也是类似情况。使用时应将它们视为“当前可用的配置项”,而非“永远不会变的稳定 API”。

安装路径选择了可控分发。 Claude Code 官方当前推荐的 Windows 安装方式是 irm https://claude.ai/install.ps1 | iex,并提供自动更新能力。OhMyWinClaude 选择通过自己的脚本体系接入 Claude Code CLI,优先追求安装路径的可控性和镜像适配。如果你更倾向跟随官方最新流程,可以只使用仓库的配置模板和 Hook 部分,安装本身走官方路径。

Hook 的安全性需要自己把关。 Claude Code 的 Hook 机制非常强大,PreToolUse Hook 甚至可以修改工具的输入参数。但这也意味着,如果 Hook 脚本有 bug 或被恶意修改,后果可能很严重。Claude Code 自身有一个安全机制:它在启动时会对 Hook 配置做快照,运行期间如果检测到 Hook 被外部修改,会发出警告并要求你在 /hooks 菜单中确认。但这不能替代你自己对 Hook 脚本的审查。


十、总结

回过头来看,OhMyWinClaude 要解决的核心问题其实就一个:怎样在 Windows 上,把 Claude Code 从“一个能跑的 CLI”变成“一个真正好用的开发工作台”。

这中间的差距,不是装几个软件就能填平的。它涉及编码环境的统一(PreToolUse Hook 注入 UTF-8)、语言服务器的可执行性(Shim Exe 部署)、Agent Teams 的 Windows 适配(psmux + 环境变量注入)、配置的分层管理(settings.json 模板 + 用户级环境变量)、插件的正确注册(LSP 二进制安装与插件声明分离)、以及国内网络环境的工程化应对。

如果你是个人开发者,这个项目最值得借鉴的是“用 Hook 和 Shim 解决 Windows 特有问题”的思路;如果你是团队负责人,更值得借鉴的是“把 AI 工具链标准化成可重复执行的环境基础设施”的方向。

真正决定 Claude Code 日常使用体验的,从来不是模型本身有多强,而是你的开发环境有多稳。

跳转微信打开