Repository navigation
fix(claude-goal-mode): quote the interpreter path baked into the adapter commands - #6250
Conversation
…ter commands The installer bakes the interpreter it selected into the `--harden` PreToolUse hook, the statusline and the `/loopx` command (including its `allowed-tools` rule) unquoted. Claude Code runs those strings through a shell, so on a host whose interpreter lives under a path with a space - the python.org all-users install, or any profile directory with a space - the shell splits the path at the first space: the hook exits non-zero with empty stdout and the tool call proceeds ungated (the documented fail-closed `should_run` gate fails OPEN), the statusline renders nothing and `/loopx` never runs. PR loopx-project#6229 named this residual. `_shell_path()` quotes a token that is not a plain path and leaves a plain path byte-identical to the previous rendering, so existing installs are unchanged. Verified on native Windows with the interpreter under a spaced directory: the exact installed hook command exits 1 with `'<dir>' is not recognized as an internal or external command` on the base commit and exits 0 with its `{}` no-op decision on this commit; the new tests fail before the change and pass after. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=xhigh
评审 head:1ee976562081e0de60d168c19d8031fa4e78b518。阻塞问题:已确认可运行的 Python 路径仍可能在生成命令时被改写。
动机
使用 Claude Code 适配器、Python 安装目录含空格的用户,需要安装后命令能够启动实际选中的解释器。原来安装成功后,工具检查、状态栏和 /loopx 命令仍可能因路径被 shell 拆开而不工作,用户必须手改配置。这个 PR 让普通空格路径可直接启动;但含美元符号或引号的合法目录名仍改变了实际执行路径,三处命令仍失败。本次边界是安装器生成的解释器命令,不是新增权限、操作系统沙箱或重新设计 Claude 的运行循环。不修复命令生成,安装成功和解释器探测成功仍不能保证用户获得可用的工具检查和设置入口。
改动思路
沿用现有安装器的解释器选择和真实启动探测,把同一个路径渲染结果交给 hook、状态栏和 slash 命令及权限前缀。保留一个位于现有宿主安装器的路径渲染器,避免为命令格式引入控制面状态;当前修复必须让已确认的路径保持字面值。当前 PR 边界是解释器命令生成、三处消费者、对应文档和原生回归;最小补修是正确转义并执行生成命令的反例。现有 install-owner 模块也有 POSIX 引号处理,但 Windows 分支输出的是 PowerShell 提示命令的调用操作符,不能未经验证直接套到 Claude 的命令字符串。Core quota、MCP argv 注册和默认启用范围均不改变。
具体改动
按改动前的 loopx/claude_goal_mode/README.md(aedb376db32a2d8a2e93686ee3800715463654e0)及 issue #6249 Expected behavior 核验。CL-1 未满足:生成命令必须启动已确认的解释器,合法美元符号或引号目录仍失败。CL-2 已实现:adapter 和 harden 保持显式启用及原 scope。CL-3 已实现:已有 deny 与第三方 hook 保留。CL-4 的 setup-only/native-loop 与 UTF-8 约定保留。CL-5 的 MCP argv 注册路径未修改,本次没有调用真实 MCP 账户。
关键代码讲解
_shell_path 在 plain-path 集合之外只加双引号,修好了普通空格分词,却没有编码字面值。hardening_block 将结果用于工具检查和状态栏;command_md 将同一结果用于实际执行行及 Bash 权限前缀,后者是否被真实 Claude 匹配未实测。README 增加了空格路径说明;原 launcher 测试增加真实普通空格启动,但字符串断言并不能覆盖展开字符。实际 PR 是这三个文件;评审使用真正 merge-base,未把更新主干中无关的 lockfile 历史算入本 PR。
对主干的风险
[P1] install.py:52 应保留路径字面值。 例如一个实际可启动的 Python 位于 User $LABEL Home,即使 $LABEL 未定义,双引号中的变量仍会展开;目录内的 " 也会改变 token。独立验证通过真正 PATH 选择、安装器 main 和安装文件回读,然后用真实 /bin/sh 执行生成命令:普通空格路径在 base 三处 rc127、head 三处 rc0;美元符号和引号路径在 base/head 都是三处 rc127,hook stdout 为空。另一个仅执行无害 printf 的命令替换夹具也失败。这是本次修复范围内保留的缺口,不冒称新增回归或已证实的实际攻击。
最小修复是在现有 helper 中按目标 shell 编码一个字面 token,并增加从实际安装文件执行的美元符号、引号和普通路径用例,兼顾 Windows 语法及权限前缀。重跑 uv run --extra test python -m pytest -q tests/test_claude_goal_install_python_launcher.py tests/test_claude_goal_mode_stdio_utf8.py tests/test_claude_goal_release_qualification.py,同时实际执行生成的三种命令。33 项原生焦点测试、repository Ruff、semantic drift 及原生 opt-in install smoke 已通过,仍不足以覆盖该反例。项目隔离、dry-run 无写入、默认不写 hook、保留规则和重复安装同态实际通过;plain-path 行为保留。没有读取或等待远程 CI,没有真实 Claude 或原生 Windows 宿主,因此没有声称其许可解析器已验收。
我的整体评价
REQUEST_CHANGES。机制规模和归属合适,普通空格用户有明确的正向改善;长程效果与完整体验仍是 not_yet_proven:合法选中路径会重复产生失效命令,用户仍需手动修复。无需新增框架或控制面状态,补足同一个局部渲染器即可;未来重构检查选择保留一个 owner。这里只新增本地词法 helper,没有新增共享状态词表或权限语义。修复后应重新核验完整 head、三处实际入口和原 scope/default/规则保留,不能仅继承本轮的普通空格成功结论。自然长期收益、真实 Claude 采用和 Windows 行为仍未测。
English verdict: REQUEST_CHANGES - 1ee9765; plain spaced paths are repaired, but confirmed literal interpreter paths with dollar/quote characters still become rc127 across the generated hook, statusline and slash command. 33 native tests, paired actual installer/shell runs, Ruff, semantic and opt-in smokes were executed; live Claude/native Windows remains untested.
| plain path; a plain path keeps the exact previous rendering.""" | ||
| if _PLAIN_SHELL_PATH.fullmatch(value): | ||
| return value | ||
| return f'"{value}"' |
There was a problem hiding this comment.
[P1] Preserve the confirmed executable as a literal shell token. A real PATH-selected interpreter under a spaced directory such as User $LABEL Home or User "quoted" Home passes _launches_python(argv), but this double-quote wrapper still expands/removes characters before execution. Running the actual installed hook/statusline/slash commands through /bin/sh returned 127 with empty hook output in both base and head; the ordinary Program Files case improves from 127 to 0. This is a retained gap in the current token repair, not a newly introduced general permission regression. Escape/encode for the actual target shell and execute generated installed commands for these literal paths while retaining plain/ordinary-space and Windows behavior; the existing string assertions do not cover this.
There was a problem hiding this comment.
已在新 head 01783cd7a9fc1688e22bf962b6b6c2efac4874f5 修复该 P1,采纳“在现有 helper 中按目标 shell 编码一个字面 token”的最小修复。
改动(loopx/claude_goal_mode/scripts/install.py):
- 新增
_is_windows_shell():os.name == "nt",即本机实际执行这些命令串的宿主 shell(Windows 为 cmd.exe——issue [Bug]: Claude adapter bakes an unquoted interpreter path, so a Python under a spaced path never starts and the --harden gate fails open #6249 的 “is not recognized” 就是 cmd.exe 的报错;其余为/bin/sh)。 - 新增
_shell_literal():POSIX 分支用shlex.quote(单引号字面量,$、内嵌"、'、\都被保留);cmd.exe 分支保持双引号(cmd 无法在引号内转义",而 Windows 路径不可能含")。 _shell_path()= 按目标 shell 的字面量编码,但 Windows 的 plain 路径(_PLAIN_CMD_PATH)保持此前不加引号的渲染,settings.json与allowed-tools权限前缀不产生 churn。- 同一条规则应用到被 bake 的脚本路径(hook / statusline /
/loopx的 entry):此前固定双引号,POSIX 下含$的插件路径同样会被展开;plain 路径渲染不变。
用例(tests/test_claude_goal_install_python_launcher.py,正是“字符串断言不足”的补足):参数化 Program Files、User $LABEL Home、User "quoted" Home(Windows 文件名不允许 ",win32 跳过、由 Linux CI 执行)与普通路径,每个用例都从实际安装文件回读并执行:settings.json 的 hook(事件 JSON 走 stdin,断言 rc 0 且 stdout 为 {})、statusline(rc 0,无目标时不渲染)、loopx.md 的 body 行($ARGUMENTS → status,断言 rc 0 且打印 no-goal 行)。另有 test_shell_path_encodes_for_posix_sh / _cmd_exe(monkeypatch 两个分支,任何平台都跑,评审 head 上会失败)和 /bin/sh 字节级 round-trip。
本机实测(Git for Windows 的真实 /bin/sh,命令串由 --harden / install_command 写出、POSIX 形态解释器路径):
- 评审 head 的双引号渲染:
Program Filesrc 0,User $LABEL Home三处全 rc 127、hook stdout 为空——与你的测量一致; - 新 head:三处全 rc 0(hook
{}、statusline 空行、/loopx status打印 no-goal 行); - 未加引号(merge base)渲染:
Program Filesrc 127; - 六种路径形态(plain、空格、
$、内嵌"、单引号、反斜杠)经/bin/sh字节精确还原。
回归对照:新测试文件对 aedb376db 的产品代码为 11 failed / 8 passed / 10 skipped(cmd 报 '<tmpdir>' is not recognized as an internal or external command),对新 head 19 passed。
重跑你列出的三个文件:38 passed, 10 skipped, 3 failed;3 个失败均为 test_claude_goal_release_qualification.py 的真实 MCP/Claude 绑定用例(WinError 193),已在 aedb376db 干净工作树同机复现,与本 diff 无关。ruff check(CI 范围)与 public-safe diff 扫描通过。
保留的残余(README 已如实记录):cmd.exe 在双引号内仍展开 %NAME%;live Claude Code 的 allowed-tools 前缀匹配仍未实测(规则文本与执行 token 保持一致,与旧渲染同一耦合);POSIX 执行用例由 Linux CI 覆盖,本机以真实 /bin/sh 手工复核。
English verdict: FIXED at 01783cd - the P1 counterexample (User $LABEL Home / User "quoted" Home) now runs the installed hook (rc 0, {}), statusline (rc 0) and /loopx (rc 0) under a real /bin/sh; every baked path token is a shell literal, cmd.exe rendering is unchanged for plain paths, and the new tests execute the commands read back from the installed files.
The review head wrapped a non-plain interpreter path in double quotes only. Under /bin/sh that is not a literal token: `$NAME` still expands inside it and an inner `"` closes the token, so a confirmed-runnable interpreter under `User $LABEL Home` or `User "quoted" Home` left the hook failing (rc 127, empty stdout - the gate fails OPEN), the statusline rendering nothing and /loopx never running. Encode every path a baked command string carries - the interpreter and the script it starts - as one literal token for the shell that actually runs the string: shlex.quote under /bin/sh, double quotes under cmd.exe. A plain Windows path keeps its previous unquoted rendering, so settings.json and the /loopx permission prefix do not churn. cmd.exe keeps one residual: %NAME% still expands inside double quotes. Tests now read the generated hook, statusline and /loopx commands back out of the installed files and execute them through the host shell for a plain path, an ordinary spaced path and literal $/quote paths. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
评审 P1 已修复(新 head
|
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=xhigh
评审 head:01783cd7a9fc1688e22bf962b6b6c2efac4874f5。原 POSIX 路径 P1 已修复;Windows 的目标 shell 判断仍阻塞。
动机
使用 Claude 适配器的用户在 Python 或插件目录含空格、美元符号时,安装可以成功,生成命令却可能无法启动 Python;POSIX 新 head 已修复,但 Windows 分支仍未对齐 Claude 的实际 shell。 原来路径在空格处拆开,用户必须手动修配置;旧评审版本加双引号仍会展开美元符号。真实 PATH 探测、安装器 main、安装文件读回及 /bin/sh 三处执行:base 5/32、旧评审 head 10/32、新 head 32/32 个组合通过;Windows 编码交给实际 Bash 词法的12组反例中9组仍无法启动 hook。 本次只处理安装器生成的命令,不新增权限、沙箱、运行循环或默认启用;不修改个人安装和活跃 Goal。 不修正实际消费 shell,成功安装仍可能重复产生没有工具决策的失效命令。
改动思路
现有 stdlib-only Claude 安装器归属正确,单一局部编码器足够;需修正实际 shell 映射。三处机器执行命令、同一个 provider 编码器、文档和持久回归;不新增控制面状态。POSIX 采用标准库 shlex.quote 是合适的最小修复,脚本路径和解释器应服从同一字面值约定。pip install-owner 输出的是 PowerShell 提示命令,其调用操作符不能直接作为 Claude shell 的推断依据。无需为了这个 provider 修复迁移 Core 或增加通用框架。
具体改动
按改动前 loopx/claude_goal_mode/README.md(aedb376db32a2d8a2e93686ee3800715463654e0)和 #6249 Expected behavior 核验。CL-1 未满足:Windows 输出未对齐实际 Claude shell;CL-2 已实现:显式 scope、adapter/harden 默认关闭;CL-3 已实现:旧 deny 与第三方 hook 保留;CL-4 已实现:UTF-8、setup-only/native-loop 与 STOP 原约定保留;CL-5 已实现:既有 MCP argv 注册代码不变,原生 qualification 测试通过。
关键代码讲解
_is_windows_shell(43行)从 os.name 推断 cmd.exe,是本次剩余问题。_shell_literal(54行)在 POSIX 用 shlex.quote,能保留美元符号、引号、单引号和无害命令替换字符;Windows 则一律双引号。hardening_block(115行)把同一解释器及脚本编码写入 hook/statusline。command_md(260行)同步 slash 执行 token 和权限前缀,保留既有完整 setup/STOP 文案。完整 diff 为3文件 +300/-9:生产 +48/-6,测试 +239/-3,README +13;测试从真正安装文件执行命令,修复了以前只检查字符串的不足。
与旧评审 head 1ee976562081e0de60d168c19d8031fa4e78b518 比较,这次增加了按平台编码及脚本路径编码;全 PR 仍从真正 merge-base aedb376db32a2d8a2e93686ee3800715463654e0 判断,未把后续无关主干历史算入修改。
对主干的风险
[P1] Windows 上不能从 os.name 判定命令由 cmd.exe 执行。 Claude 官方 Hooks reference 的 command hook fields/exec-form 段规定:未指定 args 的命令在 Windows 使用 Git Bash,没有 Git Bash 则使用 PowerShell;本 PR 没有指定 shell 或 args。subprocess.run(command, shell=True) 的 cmd.exe 测试只证明 Python 的宿主 shell,不能证明 Claude 的调用边界。
独立词法反例保留真实路径、实际生成字符串和实际 Bash 执行,仅切换本 PR 的 Windows 编码分支:12组路径/插件组合有9组 rc127、hook stdout 为空;例如合法美元符号目录在双引号中仍被展开。该探针不是原生 Windows 或 live Claude 验收;官方消费者契约加这个实际词法失败,足以说明当前 Windows 完整修复声明缺乏依据。原 POSIX 反例已关闭,并不冒称整个补修无效或已有真实攻击。最小修复:保留现有安装器 owner,按 Claude 实际执行 shell 编码或使用其支持的 argv 入口;实际 Windows/Git Bash 路径须单独验证,不能从 os.name 推断 cmd.exe。 同步纠正 README/测试的 cmd.exe 假设;支持版本与 shell 边界应明确,保留纯路径、字面路径、scope/off、重复安装和第三方规则验证。
本机当前51项焦点测试通过(32.14秒),完整 semantic126项通过(47.45秒),仓库 Ruff 范围及 opt-in smoke、diff check 通过。独立 base/旧评审/head 使用相同安装探针,32组每组执行三个实际 helper;配置保留、重复安装及默认不写 hardening 均通过。没有读取、轮询或等待 CI。作者原 Windows 红检查记录保留;本机绿结果不证明那些历史环境原因已修复。真实 Windows、PowerShell 和 Claude 权限匹配器仍未测。
我的整体评价
REQUEST_CHANGES。局部编码机制和代码规模合适,POSIX 体验已有明确正向改善;整个所称 Windows 修复的长程效果和用户体验仍是 not_yet_proven,因为实际目标 shell 被错误分类,安装与重复运行之间的缺口仍存在。长期收益不能从测试数量或经验 context 送达推断。未来重构检查选择保留现有局部 owner,先修同一消费者边界;这里没有新增共享状态词表、权限或 agent 生命周期。经验建议中的“先走真实用户路径”已用于核验 shell,恢复路线图和完整模型运行不是本 PR 的额外门槛。重新评审应执行生成配置在真实 Windows Claude 选定 shell 下的字面路径和恢复用例,并保留本轮 POSIX/off 资格。
English verdict: REQUEST_CHANGES - 01783cd; POSIX literal-path repair independently passes, but Windows is incorrectly treated as cmd.exe while Claude hooks use Git Bash/PowerShell. 51 native tests, 32 paired installed-path cases per immutable revision, semantic126, Ruff and opt-in smoke passed; Windows-rendered Bash counterfactual still fails9/12. Native Windows/live Claude remains unqualified.
| _PLAIN_CMD_PATH = re.compile(r"[A-Za-z0-9._/\\:@+-]+\Z") | ||
|
|
||
|
|
||
| def _is_windows_shell() -> bool: |
There was a problem hiding this comment.
[P1] Match the actual Claude shell rather than inferring cmd.exe from os.name. The official command-hook contract runs shell-form hooks through Git Bash on Windows, or PowerShell without Git Bash (https://code.claude.com/docs/en/hooks#command-hook-fields). This branch emits cmd-style double quotes/unquoted backslash paths instead. The real POSIX literal repair passes, but the Windows renderer through actual Bash lexing still produces rc127 with empty hook output for9/12 path combinations. Python shell=True tests qualify cmd.exe, not Claude dispatch. Correct/declare the supported consumer shell or use its supported argv form, correct README/test claims, and verify actual Windows-installed command readback under the selected Claude shell while retaining POSIX/scope/off parity. This probe is lexical execution, not a claim of native Windows qualification.
There was a problem hiding this comment.
已在新的 head 88ae94a2a 上按评审意见修复(同时把分支更新到最新 main,PR 不再落后)。
评审的意思:上一版把 Windows 上执行这些命令字符串的 shell 当成了 cmd.exe(用 os.name == "nt" 推断),但 Claude Code 的官方契约是:Windows 上 shell 形式的 hook/statusline 由 Git Bash 执行(2.1.47 起从 cmd.exe 切换),没有 Git Bash 时才回退 PowerShell(2.1.120 起),macOS/Linux 仍是 /bin/sh。因此按 cmd 风格输出的双引号/不加引号反斜杠路径在真实 shell 下是错误的——Git Bash 会吞掉反斜杠,评审用 12 组路径组合实测 9 组 rc=127 且 hook 输出为空。评审同时要求:修正 README/测试中关于 cmd.exe 的声明,并在所选 shell 下验证安装后的命令真实可执行,且保持 POSIX/作用域/未开启硬化时的行为一致。
本次改动(loopx/claude_goal_mode/scripts/install.py、README.md、tests/test_claude_goal_install_python_launcher.py):
- 新增
_git_bash():按 Claude Code 同样的来源探测 Git Bash(CLAUDE_CODE_GIT_BASH_PATH、git 安装根、PATH 上的bash、Program Files/LocalAppData 默认位置),并用uname -s(MINGW/MSYS/CYGWIN)识别,排除 System32 的 WSL bash 别名;_uses_powershell()= Windows 且无 Git Bash。 - 按目标 shell 编码:
/bin/sh/Git Bash 用shlex.quote(Windows 路径现在一律加引号,避免 Git Bash 吞反斜杠),PowerShell 用'...'(''转义)且命令位置加&。 /loopx的!行始终按 POSIX 编码(它经 Bash 工具执行);无 Git Bash 的 Windows 主机无法执行该行,README 已写明;安装/移除 Git Bash 或改CLAUDE_CODE_GIT_BASH_PATH后需重跑--harden。- README 改为真实的 shell 选择与反斜杠风险说明;测试改为在所选 shell 下执行安装后的 hook/statusline/
/loopx:用目录 junction 复现带空格的真实解释器安装(Program Files、含$与引号的目录),并有强制 PowerShell 分支通过真实powershell.exe执行。
验证:聚焦套件 26 passed, 11 skipped(11 个为仅 POSIX 用例跳过);本机真实 Git Bash 下安装后的 hook 输出 {}、statusline rc 0、/loopx status 输出 "no goal in this project yet";强制 PowerShell 分支经真实 powershell.exe 通过;ruff(改动路径)与语义词汇探针(0 candidates)通过(本机 Windows 另有 3 个与本次改动无关、在 base 同样复现的失败)。旧 head 上 test-shard 的失败位于无关的 test_quota_settlement_cli.py / test_scheduler_compat_state_key.py,本次已合入最新 main,等待新 CI 结论。
PR 保持开启、不关闭:这是有效修复,等待在 88ae94a2a 上复审;未做任何合并且无合并授权。
English verdict: the review's P1 is addressed on 88ae94a. The installer now detects the same shell Claude Code dispatches to (Git Bash when installed, PowerShell fallback, /bin/sh elsewhere), encodes every baked path as one literal token for that shell, corrects the README/test claims about cmd.exe, and executes the installed hook/statusline//loopx commands through the selected shell (real Git Bash locally, real powershell.exe for the forced PowerShell branch). POSIX scope and default-off hardening behavior are unchanged. Ready for re-review; the PR stays open and unmerged.
…apter-quote-interpreter-r6k4 Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
… shell Claude Code runs shell-form hooks and the statusline through Git Bash on Windows when Git Bash is installed, PowerShell when it is not, and /bin/sh on macOS/Linux - never cmd.exe (Git Bash switch in 2.1.47, PowerShell fallback in 2.1.120). os.name == "nt" therefore encoded for the wrong shell: Git Bash eats the backslashes of unquoted Windows paths, and the reviewer's 12-combination probe failed 9 of them with rc 127 and empty hook output. Detect the shell the way Claude Code does - CLAUDE_CODE_GIT_BASH_PATH, the Git installation, bash on PATH, the default Git for Windows locations - rejecting the WSL System32 alias by probing `uname -s` for MINGW/MSYS/CYGWIN. Encode the interpreter and the script it starts as one literal token for that shell: shlex-quoted for /bin/sh and Git Bash (Windows paths are always quoted now), and `& '...'` with `''` escaping for PowerShell. The /loopx `!` line runs through the Bash tool on every host, so it stays POSIX-encoded; a Windows host without Git Bash cannot run it at all, and re-running install.py --harden after Git Bash availability changes re-encodes for the new shell. Correct the README's cmd.exe claim and execute the installed hook, statusline and /loopx commands through the selected shell: a directory junction reproduces a real spaced interpreter install under Git Bash, and a forced PowerShell branch runs the installed strings through real powershell.exe. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · reasoning_effort=xhigh
Exact head: 88ae94a2ac080ce3c5f94359c6d1809a556c4908; immutable base: 71304fcbdd7402cfd4355fffc397079fe37d0c34. 前轮 01783cd7a9fc1688e22bf962b6b6c2efac4874f5 的完整判断没有继承。
动机
使用 Claude 适配器、解释器或插件目录含空格和引号的用户。
原来安装后的 hook、状态栏和 /loopx 可能因路径分词或展开而启动不了 Python;当前字面值编码修复了这些路径,但一个不受 Claude 接受的 shell override 仍可能让安装器选择错误语法。
POSIX 安装后读回的三个实际命令均可运行,默认 scope、旧规则和重复安装行为保持;Windows shell 选择还须补足受支持的 override 名字判断。
不修改真实个人 Claude 配置、账户、默认 LoopX 安装、Core gate、MCP 注册或 Goal 权限。
原生 Windows、PowerShell 和 live Claude 权限匹配器及长期效果未独立实测;当前具体 override 选择偏差须修复。
改动思路
保留宿主安装器这一 owner:一个局部字面量编码器共享给 hook/statusline,slash 保留其 Bash 消费端;MCP 仍用独立 argv。
当前 PR 处理既有 stdlib-only Claude 安装器的三处命令、文档和持久回归,不新增控制面状态或通用 shell 框架。
普通空格与扩展字符是已经复现的安装后启动问题,什么也不改会留下反复失效的命令。既有安装器直接编码足够,无需 Core 状态、TS迁移或新的 shell 框架。python_install_owner.py 输出的是 pip 安装提示,其 PowerShell 约定可对照,但不能替代独立消费 shell 的 Claude 字符串;standalone stdlib 边界可以保留。新 shell 探测只在安装时运行,后续环境变化要重新安装已披露;不能因此推断它必定与外部消费者的探测相同。
具体改动
按修改前 loopx/claude_goal_mode/README.md @ 71304fcbdd7402cfd4355fffc397079fe37d0c34,结合 #6249 和当前官方消费者契约核验:
- #6249 / Expected behavior:not_met,当前一个忽略 override 的输入仍生成不匹配的 shell 命令;最小修复见下。
- Install (opt-in):implemented,普通安装不启用适配器、显式 scope 要求保留。
- Optional hardening:implemented,未加 harden 不写 hook/statusline,旧 deny/第三方规则与项目范围保留。
- Setup:implemented,安装文件中的完整 setup-only/native-loop/STOP 说明与 base 字节相同,不增加工作、claim 或权限。
关键代码讲解
_git_bash/_uses_powershell 从显式 override、Git/PATH/default location 探测并选择当前渲染器,替换了前轮错误的 os.name→cmd.exe 推断,但 if override 没有先检查消费者接受的名字。_shell_literal/_shell_command 对 POSIX/Git Bash 用 shlex、对 PowerShell 用单引号及调用操作符;解释器和脚本均编码。hardening_block 在同一 shell 选择下写 hook/statusline,command_md 的 slash 仍按 Bash 处理、权限前缀与命令 token 同源。README明确无 Git Bash 的 slash 限制及环境改变后重跑;测试真正读回并执行安装文件,增加 Windows discovery和PowerShell分支。
全三文件 +478/-9、last-review-to-head 及当前完整 PR 已读。前轮 POSIX字面路径与固定cmd.exe选择问题已区分:普通 POSIX 修复独立通过;当前新问题在忽略 override 的具体输入边界,不把原修复全部说成无效。没有新增共享协议词表、receipt或权限模型。
对主干的风险
[P2,阻塞当前完整修复] 按消费者规则忽略不支持的 override 名字。 Claude 官方 environment variables规定,2.1.219起名字不是 bash.exe、sh.exe、bash 或 sh 的 override 会被忽略再自动探测。当前 _git_bash 却将任何存在的 override 加入候选,只要 uname 返回 MSYS/MINGW/CYGWIN便接受。无其他 Git Bash 时,这会让安装器输出 POSIX 单引号命令,而消费者走 PowerShell。实际 current main/settings 读回:同一可运行的空格解释器、custom-shell.exe返回MSYS,生成 hook/statusline 均缺 PowerShell调用操作符;允许的 bash.exe 对照正确。私有的一处 basename过滤候选能使原反例转为正确渲染,未修改公开head。
这是实际安装器的输入/生成结果反例,Windows platform和候选响应受控;并非原生Windows/Claude/PowerShell执行或已发生攻击。语法后果来自两个现有消费端,hooks契约与statusline契约均以 Git Bash存在与否选择 fallback;其本机真实启动资格仍需分别验证。最小修复:在现有 _git_bash 中先按当前 Claude 接受的 override basename 集合过滤,再进行候选探测与 fallback;覆盖非法 basename/无其它 Git Bash、合法 override、合法 fallback 的实际生成配置,保持三处 literal 和 off/scope/reinstall 用例。
当前三模块56 passed/3 Windows-only skipped;不借作者26/11或上一轮51作为当前执行。相同实际PATH→main→安装文件→三个/bin/sh命令的12组base/head反例中,完整三命令成功1/12→12/12,覆盖普通、空格、美元符、引号、单引号和无害命令替换形状,以及两种插件目录。完整setup/STOP文案、no-harden、scope隔离、旧deny/第三方规则和重复安装同态保留;opt-in smoke/Ruff通过。
5direct/19selected premerge命令通过,保留1maintainability advisory。初次canary的上一scope CQR stale hold未删;本次精确scope质量记录为fail,明确保留 selector反例与未解阻塞,绝不称整个gate通过。没有查询CI。真实Windows/PowerShell、Claude内部allowed-tools匹配、自然长期loop效果未测;也没有新增前端组件或独立capability开关,现有外部host的显式安装入口是这次受影响旅程。
语义与 CI 对齐
新名称是局部host helper;generic状态/权限未扩展。uname前缀不能替代消费者对override输入的闭集规则,具体误分类已经列出,补修应保持同一owner并增加其负例。新Windows渲染改变在README披露;保留原默认关闭及setup-only说明。字符串/测试计数或旧verdict不能证明真实外部宿主已经采用。
我的整体评价
REQUEST_CHANGES,交付判断 justified_increment。已证的POSIX路径体验方向为正,但完整长程和用户体验仍是not_yet_proven:这一当前选择输入可能使反复调用仍达不到原helper;先修窄的输入判定,保留通过的literal路径与scope/default证据。没有新权限、一般安全绕过或自然Windows失败宣称,也不要求无关路线图、模型试验或通用框架。
前瞻整理在现有局部selector补准闭集即可;较大的exec-form/候选整理仅为非强制设计选择。复审须对修正后的当前完整head与外部消费者输入边界核验,不继承本轮verdict。本轮未做merge、升级或真实用户安装。
English verdict: REQUEST_CHANGES - 88ae94a. Current POSIX literal commands independently pass all twelve installed-file cases (base one of twelve);56 focused tests pass with three Windows-only skips. The new selector accepts an override basename that current Claude ignores, producing POSIX instead of PowerShell fallback syntax. Match the documented basename filter and validate actual generated fallback/control cases. Platform/probe are controlled; native Windows/Claude execution and sustained benefit remain unqualified.
…nore Claude Code 2.1.219+ only honors CLAUDE_CODE_GIT_BASH_PATH when the file name is one of bash.exe, sh.exe, bash or sh (compared case-insensitively); any other name is ignored and Git Bash detection continues as if the variable were unset. The installer accepted any existing override whose `uname -s` answered MINGW/MSYS/CYGWIN, so a host with a runnable shell under an unsupported name and no other Git Bash was classified as Git Bash: the hook and statusline were baked with the POSIX single-quoted form while the consumer dispatched the same strings to PowerShell. Filter the override by the consumer's accepted names in the existing _git_bash selector and let detection continue to the same fallback sources. The new tests pin a Windows host on every platform: unsupported names are ignored, including when a git-derived Git Bash exists; the four accepted names win case-insensitively; and the generated hook/statusline carry the PowerShell `& '...'` form for the rejected override, the POSIX form for an accepted override and for the auto-detected fallback. The Windows execution test now runs that rejected-override fallback through a real powershell.exe. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
评审 P2 已修复(新 head
|
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; xhigh.
APPROVE — exact head 9056c0bfcdaaee1cd85b819409ddd8de6f69ecf4。
动机
Claude Code 适配器用户,尤其解释器或插件路径包含空格、美元符号或引号的用户。此前安装生成的命令会拆开或展开路径,无法启动 Python;现在按实际执行的 shell 保持每个路径为一个字面参数。相同12种解释器/插件路径的实际安装及命令执行,从基线1种成功变为当前12种成功;非法 Git Bash 覆盖名称不再选错命令格式。不重写静态插件清单,不改变 MCP 或宿主账户,也不声称本机完成了原生 Windows 或 Claude 内部派发验证。Windows 与真实 Claude 派发仍未在本机执行;更换宿主 shell 后需要按文档重新安装。
改动思路
Keep shell discovery and literal emission in the existing Claude installer; no Core policy/state introduced. 原 installer 仍负责明确选择的安装范围和配置合并,只修正它写出的命令。sh/Git Bash 使用 shlex.quote,PowerShell 使用单引号字面值和命令位置的 &;不把 cmd.exe 当作 Claude 的执行 shell。hook 与 statusline 共用一次 shell 选择,slash 的 ! 与其 permission token 保持 Bash 形式。
判断框架为 issue #6249 的实际路径拆分问题,以及已合并 #6229 留下的命令引用残项,固定代码基线 71304fcbdd7402cfd4355fffc397079fe37d0c34。Runnable literal interpreter/plugin tokens at hook/statusline/slash and matching permission token:实际生成文件再经 shell 执行;Current consumer shell and override basename:按独立读取的 Claude hooks reference 与 environment-variable reference 检查合法与非法分支,不用实现输出定义期望。
具体改动
完整三文件、+645/-9:install.py 的 shell 选择/字面 token,README 的 shell 与重新安装边界,以及 launcher 回归。当前全部 diff 和相邻消费者已读,最新修复与旧 blocker 分别核对。
_git_bash:67在 Windows 检查显式覆盖、Git/PATH/default 候选及可运行探测;当前增加单一局部闭集,只允许bash.exe/sh.exe/bash/sh,大小写无关。非法覆盖被忽略,仍继续自动发现。_shell_literal:113和命令位置 helper 保持每个路径为一个字面 token,PowerShell 的内嵌单引号转为两个单引号;保留$、空格、引号和反斜杠的数据含义。hardening_block:170为可选 hook/statusline 引用解释器和脚本路径;command_md:316始终为 Bash 的 slash 引用同一 token,并同步 allowed-tools。解释器仍按python3→python→sys.executable原优先级验证能启动。
上次具体 blocker 已修复:一个可运行、但名为 custom-shell.exe 的覆盖此前让 installer 输出 POSIX 字符串,而当前消费者忽略该覆盖并可走 PowerShell。此次真实 installer/main、设置文件回读及实际候选探测中,非法名忽略后输出正确 fallback,合法 bash.exe 保持 POSIX;原生分支测试还覆盖忽略后 Git 自动发现、支持名称和两种生成形式。
对主干的风险
69 项 focused 检查通过,3 项 Windows 专属检查在 macOS 跳过,保留未测结论。独立同输入基线/当前12组真实安装,再从 settings/commands 取出命令经 /bin/sh 执行:基线仅1/12组通过,当前12/12组、36个命令结果全部满足独立期望。包含普通、空格、美元符号、引号、单引号及命令替换形状的解释器路径,以及普通和含特殊字符的插件路径。每组均验证重复安装、原 deny 权限、第三方 hook 和完整 setup-only 指令不变;随后创建的未 harden 项目不产生设置,未触及其他安装范围。
Windows selector 反例在实际安装器与候选进程运行中验证,但 sys.platform 是模拟值,不能称为原生 Windows/PowerShell 或 Claude 派发实测。当前官方文档支持此次名称约束及 shell 选择;真实消费者的 allowed-tools 内部解析、平台文件系统行为仍是保留边界。README 明确无 Git Bash 的 Windows 不支持 Bash !,环境改变后须重装,没有静默承诺所有消费者都已验证。
Ruff、语义 advisory、原生 canary 5 direct / 19 selected 通过;canary 保留一项非阻塞 maintainability advisory,不称所有 advisories 都绿。严格 exact-scope quality receipt 有效。初次质量记录因 validator 引用名称不匹配被拒绝,随后仅修正引用并由 native verify 读回有效;未改业务 oracle 或把失败重标为成功。按 wait_for_ci=false 没有查询、轮询或等待 CI。
局部覆盖名称闭集属于外部消费者输入规则;本次 advisory 未识别其 frozenset,并不证明不存在语义变化。已独立检查该集合和当前消费者契约,未新建 shared Core 状态 vocabulary。默认不硬化、scope 选择、MCP、既有权限和运行指导仍由原 owner 决定,没有扩大权限。
我的整体评价
当前修复完整覆盖已声明安装器的三条实际消费路径,旧 shell 名称 blocker 已解决,批准本 head。长程效果预期正向:安装后命令能真正启动解释器,减少反复排障,并避免可选 gate 因错误 token 悄悄不运行。效率证据是相同路径组不再失败,保持重装幂等和范围隔离;不是模型 token 或吞吐提升的测量。
最强反面是安装时记录 shell,之后环境改变需要一次重新安装,且本机没有直接验证 Windows 与 Claude 内部消费。当前文档明确恢复动作;这是一项可回滚的宿主安装修复,不扩展为通用 shell 配置平台。未来整理已检查:小 helper 各有字面编码/命令位置不变量,当前保留共用函数最直接;exec-form 不能单独覆盖 statusline/slash 和旧消费者,未为此扩大重构。评审批准与合并授权分开。
English verdict: APPROVE - 9056c0b: current consumer-name filtering resolves the prior wrong-shell blocker.69 focused tests passed with3 native-Windows skips; identical real installed POSIX cases improve from1/12 at base to12/12 at head.5 direct/19 selected canaries passed with one retained advisory. Native Windows/PowerShell and live Claude dispatch remain unqualified.
Goal And Delivered Outcome
--hardenPreToolUsehook, the statusline and the/loopxcommand - including that command'sallowed-toolsrule - without quoting it. Claude Code runs those strings through a shell, so on a host whose interpreter lives under a path that contains a space (the python.org all-users installC:\Program Files\Python3xx, or any profile directory with a space) the shell splits the program path at the first space and the command never starts Python. This is exactly the host fix(claude-goal-mode): bake a launcher that actually runs Python into the Claude adapter #6229's interpreter fallback selects: whenpython3is the deadWindowsAppsalias, the installer falls back topython, which is the spaced all-users path.--hardenwrites intosettings.json, through a shell with the event JSON on stdin, exits1with empty stdout before (Claude Code treats a decision-less non-zero hook exit as a non-blocking error, so the tool call proceeds ungated) and exits0with its{}no-op decision after (real_entrypointrow). The statusline command goes fromrc=1, empty stdout torc=0with its "no goal here" line.1ee976562081e0de60d168c19d8031fa4e78b518(P1, retained gap): the double-quote wrapper is not a literal token under/bin/sh, so a confirmed-runnable interpreter under a directory named likeUser $LABEL HomeorUser "quoted" Homestill left the hook at rc 127 with empty stdout, the statusline at rc 127 and/loopxat rc 127. Fixed at01783cd7a: every path a baked command string carries - the interpreter and the script it starts - is encoded as a literal token for the shell that runs the string, and the tests execute the commands read back from the installed files.01783cd7a(P1): the shell was inferred as cmd.exe fromos.name, but Claude Code runs shell-form hooks and the statusline through Git Bash on Windows when it is installed and PowerShell when it is not (2.1.47 / 2.1.120), and through/bin/shelsewhere. Fixed at88ae94a2a:_git_bash()detects the same Git Bash sources as the consumer and rejects the WSL System32 alias by probinguname -s;_shell_literal/_shell_commandencode for the selected shell (shlex.quotefor/bin/shand Git Bash,& '...'for PowerShell); the README and test claims about cmd.exe were corrected.88ae94a2a(P2, blocking):CLAUDE_CODE_GIT_BASH_PATHwas accepted under any file name, but the consumer honors onlybash.exe,sh.exe,bashorsh(case-insensitively) and otherwise ignores it and auto-detects. A runnablecustom-shell.exewith no other Git Bash therefore produced POSIX commands while the consumer dispatched to PowerShell. Fixed at9056c0bfc: the override is filtered by the consumer's accepted names in_git_bash, detection continues to the same fallback sources, and new tests read the generated hook/statusline back for the rejected override (PowerShell form), an accepted override and the auto-detected fallback (POSIX form).main.Author Declaration
Implemented against
aedb376db./loopxcommands must start the selected interpreter on a host whose path contains a spaceloopx/claude_goal_mode/scripts/install.py_shell_command/_shell_literaltests/test_claude_goal_install_python_launcher.py::test_installed_gate_command_runs_through_a_literal_interpreter[Program Files]$or an inner"must stay one literal token for the string's shell (the round-1 rc 127 counterexample)_shell_literaltest_shell_literal_encodes_for_posix_sh,test_posix_rendering_round_trips_through_sh,test_installed_*[User $LABEL Home / User "quoted" Home]_shell_literalinhardening_block()/command_md()test_baked_surfaces_keep_the_interpreter_token_literal,test_installed_*/bin/shelsewhere_git_bash(),_uses_powershell(),_is_git_bash()test_shell_choice_follows_git_bash,test_git_bash_rejects_the_wsl_alias,test_installed_commands_run_under_powershell_without_git_bashCLAUDE_CODE_GIT_BASH_PATHnames the consumer ignores (bash.exe/sh.exe/bash/shonly, case-insensitive) are ignored here too, and detection continues to the same fallbacks_GIT_BASH_OVERRIDE_NAMES,_git_bash()test_git_bash_ignores_override_names_claude_rejects,test_git_bash_honors_override_names_claude_accepts,test_git_bash_ignores_a_rejected_name_but_keeps_detecting,test_rejected_override_renders_the_powershell_fallback,test_git_derived_fallback_renders_the_literal_posix_form/loopxpermission rule carries the same interpreter token as the command it authorizescommand_md()test_baked_surfaces_keep_the_interpreter_token_literal_python_cmd(),hardening_block(),add_hardening()tests/test_claude_goal_mode_stdio_utf8.py88ae94a2a(6 failed / 7 passed - the failures are the rejected-name and rejected-override generation cases, the passes are the accepted-name and fallback controls) versus this head (13 passed), a native-Windows execution of the rejected-override PowerShell fallback, andscripts/generate_semantic_inventory.py --changed-from HEAD(local closed set mirroring the consumer's rule; no supported new vocabulary carriers). Deliberately left out: the second residual named in fix(claude-goal-mode): bake a launcher that actually runs Python into the Claude adapter #6229 (the static plugin manifests bake a barepython3; no documented install path reaches them) and the installer's printed manual hints, which are text a user may type rather than command strings LoopX executes.Scope And Continuation
CLAUDE_CODE_GIT_BASH_PATHname rule, and the round-1$/quote counterexample and the round-3 override-name counterexample are closed.loopx/python_install_owner.py, but that module renders PowerShell guidance for pip-owned installs and importing it would add a dependency to this standalone stdlib-only installer, so the local helper stays local. The review assessed a larger candidate/exec-form reorganization as a non-required design choice, so only the consumer's override name rule was added and the discovery sources the earlier round reviewed are kept.Validation
9056c0bfcdaaee1cd85b819409ddd8de6f69ecf4staticpassedpython -m ruff check tests loopx/canary loopx/control_plane loopx/domain_packs loopx/presentation(the CI lint scope) plus the changed files: All checks passed.scripts/generate_semantic_inventory.py --changed-from HEAD: no supported new vocabulary carriers. Public/private diff scan: PASS.unitpassedtests/test_claude_goal_install_python_launcher.py: 39 passed, 11 skipped (Windows; the"quoted"directory name and the/bin/shround-trip are POSIX-only and run in CI); withtests/test_claude_goal_mode_stdio_utf8.pyandtests/test_claude_goal_release_qualification.py: 58 passed, 11 skipped, 3 failed for the pre-existing host limit below.regression_paritypassed88ae94a2a(the reviewed head): 6 failed / 7 passed; against this head: 13 passed. Round-1 history: the full file was 11 failed / 8 passed / 10 skipped againstaedb376dband has grown since.real_entrypointpassed/loopxcommands are read back from the files--harden/install_commandwrote and executed through the selected shell: real Git Bash on this Windows host for the literal-path cases (hook{}, statusline empty line,/loopx statusprints its no-goal line), a realpowershell.exefor the rejected-override fallback (the round-3 counterexample shape, including the forced-branch execution), the$/quote literal paths through a real/bin/sh(round-1 evidence), and the POSIX round-trip cases on Linux CI.manualfailedloopx canary premerge(unrelated host environment: nobashfor some smokes, Node below the required version, pip-install smokes) andpython -m mypy(loopx/control_plane/effect_runtime.py: noWNOHANGon Windows), each reproduced identically in a pristine base worktree.$/quote literal paths through a real Git Bash; the/bin/shround-trip and POSIX execution cases run on the Linux CI jobs, which also cover theUser "quoted" Homedirectory name a Windows file system cannot hold. Theallowed-toolsmatcher inside Claude Code itself is not exercised: the rule text stays equal to the token the command runs, the same coupling the previous rendering relied on. The reproductions use a launcher shim rather than a relocated CPython install; the shell-level behavior they exercise is the one the installer's own output produced.Frontend / Visual Evidence
~/.claude/<project>/.claudeconfiguration written by this opt-in installer.Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
scripts/generate_semantic_inventory.py --changed-from HEADreported no supported new vocabulary carriers.Boundary Checklist
none.Signed-off-bytrailer (git commit -s).