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

场景:Windows + pwsh 7+ 的开发环境,想让 Claude Code(以下简称 CC)默认所有 shell 操作走 PowerShell Tool 而不是 Bash Tool。只改两个文件(CLAUDE.md + settings.json),附实测命中率 100% 和性能基准数据。

省流:一句 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(告诉模型用 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`)作为选 Bash Tool 的理由——改用对应的 PowerShell cmdlet(`Get-ChildItem | Select-Object -First N``Select-String``Invoke-WebRequest``Get-ChildItem -Recurse``Get-Content`)。
- 列文件用 Glob,读文件用 Read,搜内容用 Grep——dedicated tool 优先级仍然高于 shell。
- 只有用户明确说"用 bash"或"sh"时,才可以考虑 Bash Tool;即便如此也优先 PowerShell 等价写法。
- 路径一律使用 Windows 原生风格(C:\ 或 $env:USERPROFILE),避免 / 路径。
 
## PowerShell 编码规范
- 函数/命令动词必须来自 Get-Verb(Get-、Set-、New-、Remove- 等)。
- 函数名/类名用 PascalCase,变量/参数用 camelCase。
- 优先使用 Write-Verbose / Write-Debug / Write-Information,**严禁随意使用 Write-Host**(除非必须格式化控制台输出)。
- 模块结构严格遵循:src/Public、src/Private、src/Classes + 静态 dot-source + Export-ModuleMember。
- 所有脚本必须加上 #Requires -Version 7.0。
- 错误处理统一使用 try/catch + ErrorAction Stop + Write-Error。
- 测试必须用 Pester,静态检查用 PSScriptAnalyzer。
 
## 常用指令
- 每次生成代码后自动运行 PSScriptAnalyzer 检查。
- 优先使用原生 cmdlet(Get-Command、Invoke-WebRequest、ConvertFrom-Json 等),不要自己造轮子。

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

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

三段职责:

段落作用
Shell Preference硬规则:禁止 Bash Tool + 给 PowerShell 替代。锁工具选择
PowerShell 编码规范命令风格 / 模块结构 / 规范。命令写得地道
常用指令要求用原生 cmdlet。避免绕开 Invoke-WebRequest 去调 curl

为什么放全局而非项目级

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

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

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

{
  "env": {
    "POWERSHELL_TELEMETRY_OPTOUT": "1"
  },
  "defaultShell": "powershell"
}
  • POWERSHELL_TELEMETRY_OPTOUT=1 —— 关闭 Application Insights 遥测,省掉初始化 + 退出时的异步 flush,每次约省 10-30ms。合法值 true / yes / 1(三者等价)
  • defaultShell: "powershell" —— CC settings.json 的顶级字段,控制输入框 !<command> 用哪个 shell(不是 Claude 自己的 Tool 选择)。默认是 bash,改成 powershell 之后你在 CC 输入框打 !git status 直接走 pwsh

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

方案三:permissions 配置(allow 预授权 + deny Bash 硬闸门)

CC 默认每次调用一个工具都要弹窗确认。常用工具加到 permissions.allow 后就不再弹;Bash 加到 permissions.deny 后直接被拦住,模型再想选也选不到。在 C:\Users\<你>\.claude\settings.json 加:

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

字段说明

  • allow 里的纯 Tool 名等价于”允许该工具的所有调用”;要更细粒度用 "Bash(npm *)" 这种前缀通配
  • deny: ["Bash"]硬闸门,跟 CLAUDE.md 里的”禁止调用 Bash Tool”形成双层防御:规则层失效(比如长会话注意力衰减)时,权限层兜底
  • allow 里不要再放 Bash:deny 优先级高于 allow,同时包含没有效果但会让配置看起来矛盾
  • defaultMode: "default" 保持默认询问模式;只有 allow 内的工具免确认

deny Bash 的权衡

这里跟性能基准的结论有个张力,提前说清楚:

  • 数据上 bash 在 Windows 慢 10× 以上,不 deny 也几乎不会被模型主动选上——deny 的性能收益约等于零
  • 代价:deny 之后对话里说”这次用 bash”也会被拦,escape hatch 只能靠 & bash -c "..." 在 PowerShell Tool 里嵌套(基准数据显示这条路对短命令反而更快)
  • 收益:跨会话一致性。主会话长会话里偶尔违规这种情况(见一个坑:主会话自己也会偶尔违规)被机械拦住

追求确定性就加 deny;偏好灵活 escape hatch 就把这里 deny: ["Bash"] 删掉、把 Bash 放回 allow。两条路都行得通,文章推荐默认加 deny——因为 & bash -c 嵌套的基准数据已经把 escape hatch 的替代路径解决掉了。

deny 只拦 Claude 选 Bash Tool 这个动作,不影响:Hooks 里的 bash 命令、StatusLine 的 bash -c、Skills 内部的 subprocess.spawn()、PowerShell Tool 里的 & bash -c "..."wsl bash ...。系统上 bash 依然能跑,只是模型作为 Agent 不再直接选它。

性能基准实测: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 |
| 显式 bash | "请用 bash 执行 ls -la" | Bash(用户意图优先) |
| Dedicated tool | "读一下 CLAUDE.md" | Read(不是 shell) |

实测结果(10 次调用)

| 工具 | 次数 | 场景 |
| PowerShell | 8 | 所有默认 shell 任务 |
| Bash | 1 | 用户显式说"用 bash" |
| 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 每次都是”新人”,规则记忆新鲜;主会话是”老员工”,凭惯性办事。

这也是为什么规则要写成工具层硬约束而不是命令层软偏好——规则本身要越明确越好。

常见问题

**Q: 简化 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),但不地道。在”常用指令”段加一条”禁止 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: 两条路。(1)对话里直接说”这次用 bash”,模型会切到 Bash Tool。(2)在 PowerShell Tool 里 & bash -c "..." 嵌套调用——基准数据显示对短命令反而比直接 Bash Tool 更快。

Q: 怎么定期验证规则还在生效? A: 把上面的命中率测试做成 slash command 或 skill,每次升级 CC 或改全局配置后跑一遍。

配置清单

实际要改的三处:

  • C:\Users\<你>\.claude\CLAUDE.md 顶部加入上面的”Global PowerShell Rules”三段
  • C:\Users\<你>\.claude\settings.json"env": {"POWERSHELL_TELEMETRY_OPTOUT": "1"}"defaultShell": "powershell"
  • C:\Users\<你>\.claude\settings.jsonpermissions:allow 预授权常用工具 + deny Bash(见方案三)
  • 跑一次命中率测试验证

可选:

  • 升级到 pwsh 7.5+ 享受 NativeAOT 冷启动优化(约省 50-100ms)
  • 项目 CLAUDE.md 补业务特定 shell 约定

参考

官方文档

社区资源

本 vault 相关笔记