目标:把当前会话的 token 用量统计成网页。路径:直接调
crush stats。结果:Go 在 js 平台取不到当前用户,连第一行输出都拿不到。
以下是完整的失败链与绕行方案。
把 Crush 运行时统计发布为网页。最朴素的入口是 crush stats。
$ crush stats ERROR Failed to get current user: user: Current requires cgo or $USER set in environment. exit status 1
user.Current() 是 Go 标准库「获取当前 OS 用户」的函数,
在 js/WASM 平台下默认返回 unknown 错误。文档说明两种解法:
$USER 环境变量$ USER=$(id -un 2>/dev/null || echo opfs) crush stats ERROR Failed to get current user: user: Current requires cgo or set in environment. exit status 1
id 命令在这个沙箱里不存在("id": executable file not found),
所以 $(id -un) 整体被替换成空串,USER= 是空值。
Go 的错误信息之所以显示「 set in environment」——两个单词之间恰好就是那个空字符串。
$ export USER=opfs HOME=/opfs/home; crush stats ERROR Failed to get current user: user: Current requires cgo or set in environment. exit status 1
同一行命令,错误信息完全相同。说明子进程 crush stats
看不到 我们设置的 USER。
大概率是 shell 启动器在 exec 之前过滤或重置了环境变量。
既然不能直接跑通,先看看这台机器到底有什么。
$ crush dirs /tmp/crush-runner-1 /opfs/home/.local/share/crush /etc/crush # 注意:crush 自己说在这里,但 ls 后不存在
$ ls -R /opfs/home/.crush /opfs/home/.crush ├── .gitignore ├── gear.env.md ├── init ├── logs/ # crush.log 文件存在但内容为空 │ ├── crush.log # 0 字节 │ ├── crush.log.1.crswap │ ├── crush.log.2.crswap │ └── crush.log.crswap └── skills/deploy-site/SKILL.md
crush dirs 列出 3 个目录,第三个是 /etc/crush,但实际不存在.crswap 后缀),但当前 crush.log 是 0 字节/opfs 下,是个 9P 风格的虚拟文件系统$ echo 'opfs:x:1000:1000:opfs:/opfs/home:/bin/sh' \ > /etc/passwd && cat /etc/passwd && crush stats open /etc/passwd: not implemented on js exit status 1
连 写入 /etc/passwd 都不允许——返回
not implemented on js。
在 js 平台下,重定向到 /etc/ 被 syscall 层直接拒绝。
这条路也死了。
| # | 尝试 | 结果 |
|---|---|---|
| 1 | crush stats | 失败 user.Current() 报错 |
| 2 | USER=$(id -un || echo opfs) crush stats | 失败 id 不存在 → 空字符串 |
| 3 | export USER=opfs HOME=/opfs/home; crush stats | 失败 子进程看不到环境变量 |
| 4 | echo ... > /etc/passwd | 失败 not implemented on js |
| 5 | 读 /etc/passwd | 失败 No such file or directory |
| 6 | 读 .crush/logs/crush.log | 空 文件存在但内容为 0 字节 |
user.Current() 在 GOOS=js 下需要 cgo 或显式 $USER;两者我们都做不到。mvdan/sh)在 exec 子进程前对常见敏感变量有过滤/重置行为,导致 export 无效。/etc/ 在 js 平台是只读或不可写,"制造一个 passwd 文件"这条路被 syscall 屏蔽。$ crush dirs # 报告 /etc/crush 存在 $ ls /etc/crush # No such file or directory
这说明 crush dirs 是把 可能 的全局路径拼出来,而不是真实存在的目录。
既然 crush stats 进不去 user.Current(),
就在 Crush 的 内存态 里读快照——通过 crush_info 等价的运行时接口,
把模型、provider、技能、选项、配置来源全部导出成静态 HTML。
js 构建 tag 下 fallback 到 $USER/$HOME 而非 getpwuidcrush stats 的实现解耦成"读 sqlite / 读 jsonl",避免走 user.Current()exec -a 改名后 USER=... exec crush stats(取决于 shell 启动器实现)