Claude Code 在 Windows 下强制走 PowerShell 的配置教程

场景:Windows + pwsh 7+ 的开发环境,想让 Claude Code(以下简称 CC)所有 shell 操作走 PowerShell Tool 而不是 Bash Tool。改两个文件搞定:CLAUDE.md 锁工具选择层、settings.json 把 Bash Tool 直接 deny。附实测命中率 100% 和性能基准数据。

前置:装好 PowerShell 7+

本文所有内容基于 pwsh 7+(Windows 自带的 powershell.exe 是 5.1,功能受限,不要用)。先确认或安装:

# 看本机是否已装 pwsh 7+
pwsh --version  # 输出 PowerShell 7.x.x 即可

没装的话三选一:

  • winget(推荐)winget install --id Microsoft.PowerShell --source winget
  • 手动 msigithub.com/PowerShell/PowerShell/releasesPowerShell-7.x.x-win-x64.msi
  • Microsoft Store:搜索 PowerShell 安装

完整安装指南见 官方文档。装完确认 pwsh.exe 在 PATH 里:

(Get-Command pwsh).Source  # 期望 C:\Program Files\PowerShell\7\pwsh.exe

省流:一句 prompt 让 CC 自己配好

想看清楚每一处改了什么再动手,继续往下读。

背景:CC 为什么默认会掉进 Bash

CC 在 Windows 上同时暴露 BashPowerShell 两个 shell 工具。没有明确指令时模型按任务直觉挑工具——自然语言里一旦出现 grepcurlfindls -la 这类 Unix 惯用词,就容易掉进 Bash 分支。带来的问题:

  • 路径风格变成 /c/Users/...,跟 PowerShell 脚本不一致
  • Select-String / Invoke-WebRequest / Get-ChildItem 等原生 cmdlet 被绕开
  • Windows 下的 bash 是 MSYS2(Git Bash)模拟层,fork 代价大,实测比 pwsh 慢 2.7-31 倍(见性能基准

下面三步:CLAUDE.md 写规则、settings.json 启用 PowerShell Tool、permissions 把 Bash 关进 deny。

步骤一:改 CLAUDE.md(告诉模型用 PowerShell)

C:\Users\<你>\.claude\CLAUDE.md 顶部写入三段:

# Global PowerShell Rules (Windows + pwsh 7+)
 
## Shell Preference
- **默认禁止调用 Bash Tool。** 需要执行 shell 命令时只能用 PowerShell Tool(pwsh.exe)——这是工具层的硬约束。Unix 惯用语法(`ls | head``grep``curl``find``cat`)不是借口,改用对应 cmdlet(`Get-ChildItem | Select-Object -First N``Select-String``Invoke-WebRequest``Get-ChildItem -Recurse``Get-Content`)。
- 列文件用 Glob,读文件用 Read,搜内容用 Grep——dedicated tool 优先级仍然高于 shell。
- 路径一律使用 Windows 原生风格(`C:\``$env:USERPROFILE`),避免 `/` 路径;只有在调用 git/POSIX 工具时可临时接受 `/c/...`
 
## Bash Tool 例外清单(仅在以下场景允许)
Bash Tool 在默认禁用之外,**仅**这几类情况可以使用,且必须在调用时一句话说明原因:
 
1. **用户明确指令**:"用 bash"、"跑 sh 脚本"、"在 MINGW 下执行" 等显式要求。
2. **必须依赖 POSIX 行为的脚本**:仓库里已有的 `.sh` 文件、`Makefile` 目标、`configure` 脚本——这些用 pwsh 翻译会失真,直接 Bash 调用更稳。
3. **Git hooks / pre-commit 框架**:很多 hook 假定 `/bin/sh`,pwsh 包一层反而出错。直接让 git 自己跑 Bash。
4. **跨平台 CI 脚本本地复现**:当目标是验证 `.github/workflows/*.yml` 里的 shell 步骤是否能跑通时,用 Bash 还原原始行为。
5. **MINGW-only 二进制**`ssh-agent``gpg``openssl` 等如果只在 MSYS 环境下能正常工作。
 
不在上述清单的,一律走 PowerShell。模糊地带先问用户。
 
## PowerShell 实战注意
- 文件写出默认 UTF-8 无 BOM(pwsh 7 默认行为);用 `Out-File`/`Set-Content` 时不要手动加 `-Encoding utf8BOM`,除非目标工具明确要求 BOM。
- 调用带空格路径的原生 exe 用 call 操作符:`& "C:\Program Files\App\app.exe" arg1`
- 给原生命令传 `-``@``--` 开头的参数时用 stop-parsing token:`git log --% --format=%H`
- 多行字符串(commit message、文件内容)用单引号 here-string,**闭合 `'@` 必须顶格**
  ```powershell
  git commit -m @'
  feat: add foo
 
  body line
  '@
  ```
- `-ErrorAction SilentlyContinue` 只压制输出不改退出码;要真正吞错用 `try { ... -ErrorAction Stop } catch {}`
- 严禁 `Invoke-Expression` 拼接用户输入——命令注入风险。
- 严禁 `New-Item -Force` 用在已存在的文件上,会清空内容。

关键经验:规则要锁在「工具」而非「命令」

第一版我写的是”所有 shell 命令默认使用 PowerShell”——模糊。实测模型先挑工具再想命令,跳过了规则检查,该掉 Bash 还是掉 Bash。改成「默认禁止调用 Bash Tool」后,规则作用点直接落在工具选择层,不留翻译空间。

三段职责:

段落作用
Shell Preference硬规则:默认禁 Bash Tool + 给 PowerShell 替代 + dedicated tool 优先 + 路径风格。锁工具选择
Bash Tool 例外清单列出”默认禁”之外可破例的 5 类场景。给规则留弹性,避免误伤合理用例
PowerShell 实战注意7 条 pwsh 特有避坑点(here-string 顶格、stop-parsing、注入风险等)。让生成的命令一次跑对

为什么放全局而非项目级

~/.claude/CLAUDE.md 对所有项目生效,<project>/CLAUDE.md 只管该仓库。Shell 偏好是机器/OS 级别配置,与仓库无关——别在每个新仓库重写一遍。

步骤二:改 settings.json(环境变量 + 默认 shell)

C:\Users\<你>\.claude\settings.json 加两项:

{
  "env": {
    "CLAUDE_CODE_USE_POWERSHELL_TOOL": "1",
    "POWERSHELL_TELEMETRY_OPTOUT": "1"
  },
  "defaultShell": "powershell"
}
  • CLAUDE_CODE_USE_POWERSHELL_TOOL=1 —— 官方启用 PowerShell Tool 的开关。Windows 装了 Git Bash 时这个工具默认是灰度发布,显式设 1 跳过灰度直接启用;Linux/macOS/WSL 同样用这个变量启用(前提是装好 pwsh 7+ 并加到 PATH)。设 0 可在 Windows 上反向退出灰度
  • POWERSHELL_TELEMETRY_OPTOUT=1 —— 关闭 Application Insights 遥测,省掉初始化 + 退出时的异步 flush,每次约省 10-30ms。合法值 true / yes / 1(三者等价)
  • defaultShell: "powershell" —— CC settings.json 的顶级字段,控制输入框 !<command> 用哪个 shell(不是 Claude 自己的 Tool 选择,也不影响 hook 命令——见步骤四陷阱说明)。默认是 bash,改成 powershell 之后你在 CC 输入框打 !git status 直接走 pwsh

官方合法值见 about_TelemetryPowerShell Tool docs。这些必须在 pwsh 启动前设置,所以必须放 settings.json 的 env 段。

步骤三:permissions 配置(allow 常用工具 + deny Bash)

CC 默认每次调用一个工具都要弹窗确认。常用工具加到 permissions.allow 后就不再弹;想彻底关掉某个工具,放进 permissions.deny ——比 prompt 规则和 hook 拦截都干净。在 C:\Users\<你>\.claude\settings.json 加:

{
  "permissions": {
    "allow": [
      "PowerShell",
      "Read",
      "Write",
      "Edit",
      "MultiEdit",
      "Glob",
      "WebFetch",
      "WebSearch",
      "NotebookRead",
      "NotebookEdit"
    ],
    "deny": [
      "Bash"
    ],
    "defaultMode": "default"
  }
}

字段说明

  • allow 里的纯 Tool 名等价于”允许该工具的所有调用”;要更细粒度用 "PowerShell(git *)" 这种前缀通配
  • Bash 放进 deny:把 Bash Tool 在工具层直接关掉。CC 不再把 Bash 当作可选项,主会话和子 agent 都没法绕过——比 prompt 提醒或 PreToolUse hook 更彻底,也省掉一次 pwsh 冷启动开销
  • defaultMode: "default" 保持默认询问模式;只有 allow 内的工具免确认

真要 Bash 的时候怎么办

CLAUDE.md 里那份例外清单 5 类场景(.sh 脚本、Makefile、Git hooks、CI 复现、MINGW-only 二进制)仍然成立,只是触发方式变了:

  1. 能用 pwsh 嵌套就用& bash -c "..." 在 PowerShell Tool 里跑,对短命令反而更快(见反直觉发现
  2. 必须用 Bash Tool 才行:临时把 settings.json 里 deny 数组中的 "Bash" 删掉,重启 CC;用完加回去
  3. 直接调 exe 绕开 shell:很多场景其实只是想跑某个程序,直接 & "C:\Program Files\Git\bin\bash.exe" script.sh 就行,根本不需要 Bash Tool

性能基准实测:pwsh vs bash

命中率回答”对不对”,基准回答”快不快”。本节数据跑在 Windows + pwsh 7.6 + MSYS2 Git Bash 上,6 场景 × 2 shell × 3 次串行调用,丢 run 1 冷启动,取 run 2/run 3 平均。

前提声明

  • 测试机:Windows + pwsh 7.6 + MSYS2 Git Bash
  • 测试目录:同一物理目录(60 md / 21 json / 277 文件)
  • 重要偏差:bash 命令里的 $(date +%s%3N) 在 MSYS2 下是 fork 子进程,单次约 150-300ms;pwsh 的 [DateTimeOffset]::UtcNow 是进程内调用约 0ms。所以 bash 的 EXEC 包含 2 次 date fork(约 400-600ms),下表 bash 数字系统性偏高 ~500ms

结果表(EXEC_MS,丢冷启动后 run2/run3 平均)

场景pwsh avgbash avg倍数差
1. Baseline(echo)1133102.7×
2. 单文件读41105925.8×
3. FS 遍历(60 md)51159331.2×
4. 流式 grep5194318.5×
5. 管道链(top-5 大)72134018.6×
6. JSON 解析4082720.7×

pwsh 全场胜,差距 2.7-31.2 倍。即便扣掉 bash 的 date fork 污染,bash 仍然慢 10× 以上,量级结论不变。

反直觉发现:& bash -c 嵌套反而更快

在 PowerShell Tool 里调 & bash -c "wc -l < CLAUDE.md" 耗时约 748ms,直接用 Bash Tool 做同样的事是 1059ms——嵌套比直接更快。原因:直接 Bash Tool 里有 2 次 $(date) fork,嵌套路径只 fork 1 次 bash.exe。

实战含义:pwsh 流程里偶尔掺 Unix 工具时,& bash -c "..." 一次性吞比切到 Bash Tool 便宜。

适用范围:短命令(<100ms 命令体)差异明显;长命令(>1s)里命令体才是大头,选哪个都差不多。

简短分析

为什么 bash 在 Windows 上这么慢? 根因是 MSYS2 用 CreateProcess + COW 模拟 POSIX fork(),且所有文件操作都走 POSIX→Win32 翻译层。pwsh 的 Get-ChildItem 直接调 FindFirstFile,跳过整个翻译层。场景 3 FS 遍历 51ms vs 1593ms 的 31× 差距,大头就是这层翻译。

JSON 场景(6):pwsh 的 ConvertFrom-Json 是进程内解析(40ms),bash 要 fork 出 jq 子进程(827ms)。本机 jq 用 scoop 装好了,没有”缺工具”摩擦——纯 fork 代价就能把差距拉开一个数量级。

结论:pwsh 不是风格选择更好,是在 Windows 上快一个量级

Unix → PowerShell 命令速查

| Unix | PowerShell |
| ls / find | Get-ChildItem |
| grep | Select-String |
| cat | Get-Content(或直接用 Read 工具) |
| head -N / tail -N | Select-Object -First/-Last N |
| wc -l | Measure-Object -Line |
| sort / uniq | Sort-Object / Group-Object |
| tr / 简单 sed | -replace 运算符 |
| curl / wget | Invoke-WebRequest / Invoke-RestMethod |
| xargs | 天然管道对象流 |
| awk 复杂脚本 | ForEach-Object { ... } + -split(pwsh 更啰嗦) |
| sed -i 原地改 | (Get-Content f) -replace '...','...' \| Set-Content f |
| Heredoc <<EOF | Here-string @"..."@ / @'...'@ |

真正必须 bash 的场景(.sh 脚本、POSIX-only 工具链、Makefile 里的 bash 习惯用法),在 PowerShell Tool 里 & bash -c "..." 一行嵌套即可。

验证:命中率测试

派 subagent 跑独立任务(每个 agent 干净上下文,不会被主会话污染),让它自报调用的工具。

Agent Prompt 模板

<任务描述,自然语言>

完成后在回复最后加一行 `TOOL_USED: <工具名>`,工具名必须严格是以下之一:
`PowerShell`、`Bash`、`Grep`、`Read`、`Glob`、`Other:<名字>`。
按你实际调用的工具如实填写,不要猜。

回复控制在 120 字以内。

在主会话里并行派发多个 Agent Tool 调用,收集 TOOL_USED 汇总。

测试场景举例

| 场景 | 自然语言 | 期望工具 |
| Git 操作 | "看一下 git status 和当前分支" | PowerShell |
| Unix 词陷阱 | "grep 一下 CLAUDE.md 里的 shell" | PowerShell/Grep |
| curl 陷阱 | "curl 一下 example.com" | PowerShell |
| Dedicated tool | "读一下 CLAUDE.md" | Read(不是 shell) |

Bash 已在 deny 列表,无需测试”显式说用 bash”——CC 会直接拒绝调用。

实测结果

| 工具 | 次数 | 场景 |
| PowerShell | 8 | 所有默认 shell 任务 |
| Read / Grep | 2 | Dedicated tool 竞争正确路由 |
  • 默认 shell 任务 PowerShell 命中率:100% (8/8)
  • Unix 词陷阱抗性:3/3(grep、curl、find+sort 全部没掉进去)
  • Dedicated tool 路由:2/2(Read/Grep 没被 shell 抢走)

一个坑:主会话自己也会偶尔违规

实测子 agent 命中率 100%,但主会话自己ls resources/ | head -20 上触发过 Bash Tool。根因是规则写在 system prompt 顶部,中间穿插了大量 tool output 和文件内容后,注意力会衰减。子 agent 每次都是”新人”,规则记忆新鲜;主会话是”老员工”,凭惯性办事。

这也是为什么规则要写成工具层硬约束而不是命令层软偏好——规则本身要越明确越好。步骤三的 deny 列表 就是把这条硬约束推到工具注册层:CC 根本看不到 Bash 这个选项,主会话偶尔走神也无从下手。

常见问题

Q: 简化 $PROFILE 能加快 CC 调 pwsh 吗?

A: 不能。实测用 (Get-CimInstance Win32_Process -Filter "ProcessId=$PID").CommandLine 在 CC 里跑,CC 启动 pwsh 的命令行是 pwsh.exe -NoProfile -NonInteractive -Command "...",profile 根本不加载。简化 profile 只会加快你手动开终端。

Q: 路径风格混乱(/c/Users vs C:\Users)?

A: Shell Preference 里已有”路径一律 Windows 原生风格”。如果还是出现 /c/...,通常是模型从 bash 历史输出里复制了路径;在项目 CLAUDE.md 里再强化一次即可。

Q: 模型还是会写 ls | grep xxx

A: 这管道在 pwsh 下能跑(ls 是 Get-ChildItem 别名,grep 需要换 Select-String),但不地道。在 Shell Preference 段加一条”禁止 Unix 别名管道,用 cmdlet 全名”可以进一步修正。

Q: Dedicated tool(Read/Glob/Grep)和 shell 谁优先?

A: Dedicated tool 优先。读文件用 Read(支持分页/图片/PDF),找文件名用 Glob(按修改时间排序),搜内容用 Grep(基于 ripgrep,比 Select-String 快)。Shell Preference 第三条就是这个意思。

Q: 子 agent 会继承 Shell Preference 吗?

A: 会。CLAUDE.md 通过 system prompt 注入,user-level 和 project-level 都会继承到子 agent。实测 6 个独立 subagent 全部正确应用。例外:如果你在 Agent Tool 调用里传了自定义 system 字段覆盖,继承链断开,需要在自定义 system prompt 里重申规则。

Q: 需要 bash 跑一段命令怎么办?

A: Bash Tool 在 deny 列表里,对话里说”用 bash”也调不动。三条路:(1)在 PowerShell Tool 里 & bash -c "..." 嵌套调用——基准数据显示对短命令反而比直接 Bash Tool 更快;(2)直接调可执行文件 & "C:\Program Files\Git\bin\bash.exe" script.sh,绕开 Bash Tool;(3)真要长期用 Bash Tool,从 settings.json 的 deny 里临时删掉 "Bash" 重启 CC,用完再加回。

Q: 怎么定期验证规则还在生效?

A: 把上面的命中率测试做成 slash command 或 skill,每次升级 CC 或改全局配置后跑一遍。Bash 进 deny 后这个测试主要变成”PowerShell vs dedicated tool”的路由验证。

Q: 为什么不用 PreToolUse hook 拦截?

A: 试过。Hook 方案是给命令首 token 做 denylist(ls/cat/grep 等),命中就 deny + 提示替代命令。问题:每次触发要冷启动 pwsh 跑脚本(150-300ms),还得维护 denylist 和日志收敛曲线,而且只能拦反射性单 token,绕过路一堆(bash -c "..."cd && lsFOO=bar ls)。把 Bash 直接放进 permissions.deny 之后,CC 在工具注册阶段就排除了 Bash——模型连选项都看不到,零开销零维护、零绕过路径。Hook 适合”想拦但又想留弹性”的场景;想彻底关掉就用 deny。

配置清单

  • 装好 pwsh 7+(前置
  • C:\Users\<你>\.claude\CLAUDE.md 顶部加入”Global PowerShell Rules”三段(步骤一)
  • C:\Users\<你>\.claude\settings.json"env": {"CLAUDE_CODE_USE_POWERSHELL_TOOL": "1", "POWERSHELL_TELEMETRY_OPTOUT": "1"} + "defaultShell": "powershell"(步骤二)
  • C:\Users\<你>\.claude\settings.json 把 PowerShell + Read/Write/Edit/Glob 等放进 permissions.allow,把 Bash 放进 permissions.deny(步骤三)
  • 跑命中率测试验证 PowerShell vs dedicated tool 路由正确

可选:

  • 升级到 pwsh 7.5+ 享受 NativeAOT 冷启动优化(约省 50-100ms)
  • 项目 CLAUDE.md 补业务特定 shell 约定
  • 例外清单 5 类场景出现时,临时把 Bash 从 deny 移走(或用 & bash -c "..." 嵌套绕过)

参考

官方文档

社区资源

本 vault 相关笔记