一句话
产品自己生成的 config.toml 里写的是绝对路径,而它自己的 pre-spawn 审计要求那一格恰好是裸字符串 "bun"。
两者由同一份代码在同一次启动里产生,不可能同时满足 ⇒ 现在 prepare 出来的共存节点,
过不了自己的审计。
证据链(四段,每段都可复验)
① 生成侧:解析器永远不可能 返回裸命令名
agent-node/src/runtime/grok-build-cli-home.ts:60:
export function resolveGrokCommhubMcpCommand ( command : string , pathEnv : string ) : string {
const candidates = isAbsolute ( command ) ? [ command ]
: pathEnv . split ( delimiter ) . filter ( Boolean ) . map ( ( directory ) => resolve ( directory , command ) ) ;
for ( const candidate of candidates ) {
try {
const canonical = realpathSync ( candidate ) ;
if ( ! statSync ( canonical ) . isFile ( ) ) continue ;
accessSync ( canonical , constants . X_OK ) ;
return canonical ; // ← 唯一的成功返回,realpath 之后的绝对路径
} catch { }
}
throw new Error ( "grok copresence CommHub MCP command could not be resolved or executed" ) ;
}
只有两种出口:绝对规范路径,或抛错。 裸 "bun" 不在其中。
② 那个值被原样写进 config.toml
agent-node/src/cli.ts:3682:
const commhubMcpCommand = resolveGrokCommhubMcpCommand ( process . env . BUN_BIN || "bun" , process . env . PATH || "" ) ;
agent-node/src/runtime/grok-build-cli-home.ts:1471:
`command = ${ JSON . stringify ( commhubMcp . command ) } ` ,
(同一文件 :1067 还额外强制 realpathSync(command) === command,也就是必须 canonical。)
③ 真 grok 0.2.93 把它原样报成 target(实测,不是推断)
在容器里装钉死版本、--network none 离线跑(宿主那份是 1.0.5,且其符号链接被 63 个在跑节点共用,没有动):
容器内 grok --version → grok 0.2.93 (f00f96316d)
config.toml: command = "/usr/local/bin/fake-bun"
$ grok inspect --json
"mcpServers": [{
"name": "commhub", "transport": "stdio",
"target": "/usr/local/bin/fake-bun",
"source": { "type": "configToml", "path": "/tmp/gh/config.toml" }
}]
⇒ target = config.toml 的 command,逐字,绝对路径。
④ 审计要求它恰好是裸 "bun"
agent-node/src/runtime/grok-copresence/runtime.ts:550:
|| commhubRecord . target !== "bun"
而 beforeSpawn 是无条件 挂上的(agent-node/src/cli.ts:3844):
beforeSpawn : ( ) => auditAndPrepareRuntime ( ) . env ,
所以每次 spawn 都跑这条审计,失败即 GrokCopresenceFailure: grok copresence pre-spawn audit failed。
磁盘上的现状与这条推论一致
本机 ~/.anet-grok/node-*/config.toml(只读了 command 那一行):
node-47d475f619473b592af34838 command = "bun" ← 旧代码写的,能过审计
node-a2f3e13fd1cab3e4f498a3a3 command = "bun"
node-c05a2744ac212c3f0c4ab709 command = "bun"
node-1a793d6b1274fe11f641001c command = "/home/vansin/.nvm/…/bun/bin/bun.exe" ← 绝对路径,按 :550 会被拒
其余 2 个没有 [mcp_servers.commhub] 段
node-1a793d6b… 的 session 目录是 %2Fhome%2Fvansin%2Fgrok-commdog-workspace ⇒ 通信狗 。
而当前 5 个 grok 共存节点(A站狗 / P站狗 / 通信狗 / 指挥狗 / TM宿舍狗)全部 status=offline 。
🔴 我不声称这就是每一个节点 offline 的原因 —— 那需要各自的启动日志。
上面成立的是一条结构性推论 :用当前代码 prepare 出来的配置,必然过不了当前代码的审计。
三个 command = "bun" 是历史遗留(旧版本写的),它们只要不重新 prepare 就还能跑;
一旦重启并重写 config.toml,就会变成绝对路径,然后被拒。
影响范围
版本上:那行审计由 8167c494(2026-08-04)引入;@sleep2agi/agent-node@2.5.0-preview.31
发布于 2026-08-12 ⇒ 在跑的 preview 含这行 。main 同样含。
也就是说 main 和在跑的 preview 都在这个状态 ,不是「main 已修、线上未部署」。
不要按直觉修
有两个方向,选错会把现在还能跑的那三个也弄坏 :
把审计改成「和解析后的绝对命令比」(= 被我从 fix(grok): 把 #867 落到 main —— 含 #883(P0) 点名的修复,并修掉它自带的一处能力边界回归 #1010 摘掉的 7914755a 的方向)——
对新配置正确,但会立刻拒掉那三个 command = "bun" 的旧配置 ;
让审计同时接受两种形态 (把 target 也按 PATH 解析一次再比)——
兼容旧配置,代价是审计逻辑变复杂。
⚠️ 还有一件必须先确认的:那三个旧配置是「历史遗留」还是「某条代码路径仍会写裸 bun」 。
如果是后者,方向 1 会周期性地反复弄坏节点。我没有查到写裸 "bun" 的路径
(resolveGrokCommhubMcpCommand 排除了这个可能),但**「我没找到」不等于「不存在」**。
#1011 是同一根因的另一半(夹具把 target 写死成 "bun",所以这个矛盾在 CI 里永远看不见)。
本条记的是后果 :产品起不来。两条一起看才完整。
一句话
产品自己生成的 config.toml 里写的是绝对路径,而它自己的 pre-spawn 审计要求那一格恰好是裸字符串
"bun"。两者由同一份代码在同一次启动里产生,不可能同时满足 ⇒ 现在 prepare 出来的共存节点,
过不了自己的审计。
证据链(四段,每段都可复验)
① 生成侧:解析器永远不可能返回裸命令名
agent-node/src/runtime/grok-build-cli-home.ts:60:只有两种出口:绝对规范路径,或抛错。 裸
"bun"不在其中。② 那个值被原样写进 config.toml
agent-node/src/cli.ts:3682:agent-node/src/runtime/grok-build-cli-home.ts:1471:(同一文件
:1067还额外强制realpathSync(command) === command,也就是必须 canonical。)③ 真 grok 0.2.93 把它原样报成
target(实测,不是推断)在容器里装钉死版本、
--network none离线跑(宿主那份是1.0.5,且其符号链接被 63 个在跑节点共用,没有动):⇒
target= config.toml 的command,逐字,绝对路径。④ 审计要求它恰好是裸
"bun"agent-node/src/runtime/grok-copresence/runtime.ts:550:而
beforeSpawn是无条件挂上的(agent-node/src/cli.ts:3844):所以每次 spawn 都跑这条审计,失败即
GrokCopresenceFailure: grok copresence pre-spawn audit failed。磁盘上的现状与这条推论一致
本机
~/.anet-grok/node-*/config.toml(只读了command那一行):node-1a793d6b…的 session 目录是%2Fhome%2Fvansin%2Fgrok-commdog-workspace⇒ 通信狗。而当前 5 个 grok 共存节点(A站狗 / P站狗 / 通信狗 / 指挥狗 / TM宿舍狗)全部
status=offline。🔴 我不声称这就是每一个节点 offline 的原因 —— 那需要各自的启动日志。
上面成立的是一条结构性推论:用当前代码 prepare 出来的配置,必然过不了当前代码的审计。
三个
command = "bun"是历史遗留(旧版本写的),它们只要不重新 prepare 就还能跑;一旦重启并重写 config.toml,就会变成绝对路径,然后被拒。
影响范围
8167c494(2026-08-04)引入;@sleep2agi/agent-node@2.5.0-preview.31发布于 2026-08-12 ⇒ 在跑的 preview 含这行。main 同样含。
不要按直觉修
有两个方向,选错会把现在还能跑的那三个也弄坏:
7914755a的方向)——对新配置正确,但会立刻拒掉那三个
command = "bun"的旧配置;target也按 PATH 解析一次再比)——兼容旧配置,代价是审计逻辑变复杂。
如果是后者,方向 1 会周期性地反复弄坏节点。我没有查到写裸
"bun"的路径(
resolveGrokCommhubMcpCommand排除了这个可能),但**「我没找到」不等于「不存在」**。与 #1011 的关系
#1011 是同一根因的另一半(夹具把
target写死成"bun",所以这个矛盾在 CI 里永远看不见)。本条记的是后果:产品起不来。两条一起看才完整。