车东 Che, Dong

Sun 20 September, 2026

23:41 fix: MCP 信封也要暴露限额(formatMcpStructured 是白名单式挑字段) » Recent Commits to phpman:master
fix: MCP 信封也要暴露限额(formatMcpStructured 是白名单式挑字段)

formatMcpStructured() 只挑固定字段返回,所以加在 IR 顶层的
content_truncated / content_budget_bytes / content_retained_bytes 在 MCP 响应里
被丢掉了——staging 实测 ?format=json 有标记、/mcp 没有。section 级标记因为
sections 整体透传所以没受影响。

- src/format_mcp.php: formatMcpStructured() 在截断时透出三个字段;
  formatMcpMarkdown() 的收尾说明在截断时改为告知「本节文本已封顶,
  带 truncated 标记的条目附 content_bytes/content_lines,章节列表完整」,
  不再承诺不再携带的 full documentation
- test/integration/test_json_content_cap.php: 补 5 条 MCP 信封断言

验证:7 个页面的 MCP 信封与改动前逐字节一致(含 perlfunc 324KB subsection);
测试 384 → 389 通过。
23:33 feat: json/mcp 载荷限额,派生字段改为折行时单遍计算 » Recent Commits to phpman:master
feat: json/mcp 载荷限额,派生字段改为折行时单遍计算

info py 的 section 正文共 14.4MB(单 "Index" 一节就有 1.37MB),原本要整页
驻留内存才能序列化。现在折行阶段就按预算丢弃超出部分:全局 1MB
(PHPMAN_JSON_MAX_CONTENT_BYTES) + 单节 512KB (PHPMAN_JSON_MAX_SECTION_BYTES),
两个常量都可在 phpman.config.php 覆盖。

因为超出预算的正文不再驻留,summary/synopsis/flags/examples/see_also 改为在
折行时从完整行流里累积,而不是事后从 sections 再扫一遍——否则被丢弃的
OPTIONS/EXAMPLES 正文会让 flags 丢失。被截断的条目带 truncated /
content_bytes / content_lines,顶层带 content_truncated / content_budget_bytes
/ content_retained_bytes;section_outline 仍然完整,导航不受影响。

单节上限取 512KB 是实测结论:73 个常见 man 页里 perlfunc 有 324KB 的
subsection(bash 36KB、sudoers 59KB 的 section),32KB 会误伤这类总量远低于
预算的正常页面。放得下的页面必须逐字节不变。

- src/format_json.php: buildJsonData() 折行时按预算保留正文并累积派生字段
- src/config.php: 新增两个限额常量
- phpman.config.php.example: 记录这两个开关
- test/integration/test_json_content_cap.php: 17 条断言覆盖标记、预算上限、
  数组/非 JSON 边界,以及最关键的「截断后派生字段仍完整」

验证:62 个 man 页 + perldoc 与改动前逐字节一致(脚本比对 HEAD 与工作区);
小预算下 flags/summary/examples/see_also 与不限额时完全相同;真实 info py 在
生产环境 PHP 8.5 下 JSON 16.1MB → 2.88MB(-82%)、峰值 67.4 → 46.5MB。
23:10 fix: 索引的 url 字段指向可用的地址(/man/json 实际是页面查询) » Recent Commits to phpman:master
fix: 索引的 url 字段指向可用的地址(/man/json 实际是页面查询)

getManIndex() / getInfoIndex() 输出的 url 自引用写的是 /man/json、/info/json,
但索引没有 command 段,PATH_INFO 表达不了格式——这两个地址会被路由成「名为
json 的页面」并返回 HTML。索引取格式只能走查询参数,phpMan.php 里页脚的格式
链接用的就是 ?mode=X&format=json。

- src/source_man.php / src/source_info.php: url 改为 ?mode=man|info&format=json

验证:四个地址均返回合法 JSON(man 索引 count=10、info 索引 count=195,
mcp 形式为合法信封)。
23:01 fix: 大页面的 MCP/JSON 不再经由 IR 字符串(info py 内存超限) » Recent Commits to phpman:master
fix: 大页面的 MCP/JSON 不再经由 IR 字符串(info py 内存超限)

info py 是 430k 行 / 19.5MB 的页面。MCP 路径原本把 IR 序列化成 ~15MB 字符
串、再由 formatForOutput() 解码回来,转换过程中同时持有三份整页文本,在
128MB memory_limit 下直接 fatal(生产日志累计 8 次,最近 02-Sep)。

- src/format_json.php: formatToJSON() 拆出 buildJsonData(): array;行缓冲折
  进 $sections 后立即释放,编码前 unset($sections)。释放必须通过引用写空
  ($lines = array())——unset() 只去掉本地别名,调用方的数组仍然存活
- src/format_mcp.php: 抽出 formatMcpEnvelope(array),新增 formatPageOutput()
  —— mcp 直传数组,json 走原路径;数组已在手的 search/索引页同样跳过
  encode+decode
- source_{man,info,perldoc,pydoc,ri}.php: 9 处调用点改走 formatPageOutput
- test/integration/test_formatter_mcp.php: 锁定「数组路径 == 字符串路径」

验证:新旧路径输出字节级一致(mcp/json × 页面/索引,仅 generated 时间戳因
跨秒不同);info py chunk 预留峰值 127.4 → 104.0MB,peak_real 不变(67.4MB);
staging 真实请求 3×200,日志零新增。
23:01 fix: ?debug=1 不再为追加 _profiling 键重序列化整个响应 » Recent Commits to phpman:master
fix: ?debug=1 不再为追加 _profiling 键重序列化整个响应

phpMan.php 原本用 json_decode() + json_encode() 给 json/mcp 响应加一个
_profiling 键。对 info py 这种 19.5MB 的响应,decoded 数组本身就要 44MB,
单独测峰值 62MB real / 127.6MB chunk,自己就贴着 128MB 上限——staging
(PHPMAN_DEBUG=true)对该页面因此稳定 500,而生产因为 debug 关闭不受影响。

- src/cache.php: 新增 appendProfilingJson(),把 _profiling 注入到已序列化的
  JSON 对象末尾(结果仍是合法 JSON);JSON 数组与非 JSON 响应原样返回,
  避免旧实现把数组响应悄悄变成对象
- phpMan.php: 改调 appendProfilingJson()
- test/unit/test_profiling_append.php: 15 条断言覆盖普通/紧凑/空对象、
  尾随空白、JSON 数组、非 JSON、空响应、2000-section 大响应

验证:staging(debug 开启)连续 3 次未缓存请求全部 200,JSON 合法、
_profiling 为最后一个键、10757 个 section 完整,日志零新增。
00:23 fix: FTS5 查询把名字里的 and/not 误判为操作符(no such column) » Recent Commits to phpman:master
fix: FTS5 查询把名字里的 and/not 误判为操作符(no such column)

\b(AND|OR|NOT|NEAR)\b 在 "placeholders-and-bind-values"、
"SQL::Statement::Operation::And" 这类名字里同样成立,整串被原样返回、
未加引号;而未加引号的 -/: 让 FTS5 把后一个词读作列名,报
"no such column: and|not|SQL|forwarding"。生产日志累计 134 条该报错,
搜索静默降级到 apropos,丢失 FTS5 排序与前缀匹配。

- src/search_index.php: buildFtsQuery() 改为按空白切分后整词比对操作符
  - AND 与隐式连接等价、裸 NEAR 非法(需 NEAR(a b, N)),不再参与拼接
  - OR/NOT 仅在两个词之间保留;开头/结尾/连续出现时丢弃而非报错
  - 引号短语判断前移,避免 "a AND b" 被拆开
  - 其余词仍走 "term"* 引号路径(原有行为不变)
- test/unit/test_search_fts.php: 12 条字符串级回归断言
- test/unit/test_search_fts_db.php: 9 条真实 FTS5 表执行回归断言

验证:21 个输入对照新旧实现,旧实现 10 个报 FTS5 错误、新实现 0 个;
测试 326 → 347 通过,0 失败。

Mon 14 September, 2026

18:20 OpenClaw 2026.9.3→2026.9.4 升级反复失败:package swap 完整性扫描 30s 超时(绕过 + 根治) » 笔记 by 车东

openclaw update 连续 4 次失败在 global install swap 步骤:retained package tree changed / Installation recovery is unverified,而安装树事后与 pristine(npm pack 解包)逐字节一致。根因是 swap 的完整性扫描带一个 30s 绝对截止(MAX_SCAN_MS=3e4),本机 454MB / 35050 文件的安装树在冷缓存 + 并发 I/O 下扫不完 → 超时抛错 → previousRoot 未定义 → 二次校验误报「树变了」。失败记录只显示错误尾部,真正的超时被截断吞掉,极难定位。绕过用 npm install -g openclaw@2026.9.4,根治把 MAX_SCAN_MS 补丁到 120s(注意:每次升级 npm 会覆盖补丁,需重打)。

TLDR

  1. 现象:升 2026.9.4 时 openclaw update 稳定卡在 global install swap exit 1,失败记录 stderrTail 只有 ...: retained package tree changed Installation recovery is unverified; inspect the installation and backups in /usr/local/lib/node_modules before restarting.
  2. 根因(上游 bug):swap 的 createPackageIntegrityReaderMAX_SCAN_MS = 30s 的绝对 wall-clock 截止包住每个 I/O。完整扫描安装树:热缓存 13–16s、冷缓存 ~23s、真实更新条件(刚暂存 454MB 候选包 + 系统在扫新文件)>30s → 抛 Package rollback verification timed out。此刻 prepareBaselinepreviousRoot 还是 undefinedrestoreSwap 的二次校验 !previousRoot 就误报 retained package tree changed。失败记录把 errors 数组从尾部截断,根因那一行被吃掉,只剩 restoreSwap 的次级消息。
  3. 绕过:候选包 7 项校验全部通过(doctor lint / migration rehearsal / canary 等),直接 npm install -g openclaw@2026.9.4。npm 11 默认挡 install scripts,要带 --allow-scripts=openclaw,... 或配 npm config set allow-scripts=...,否则 koffi 原生库 / bundled-plugins postinstall 不会跑。
  4. 后续两个坑
    • 插件 preflight:cron 里 targetVersion 为空时报 plugin-target-unavailable(feishu/opencode 无显式 target)。手动 openclaw plugins update --all@latest 把 feishu、opencode 升到 2026.9.4 后,openclaw update --dry-run 的插件解析即通过。
    • 配置 schema:2026.9.4 的 feishu schema 里 channels.feishu.groupPolicy 必须是 open / disabled / allowlist / allowall,旧的 disable 值会让 gateway 拒绝启动。
  5. 根治:把 dist/update-runner-*.mjs 里的 const MAX_SCAN_MS = 3e4; 补丁为 120000(120s),cron 的 openclaw update --yes 下次才能真正过 swap。

现象

openclaw update 在 validating 阶段 7 项候选校验全过(global update 57.8s、migration rehearsal、doctor lint、config validation、plugin resolution、migration continuation、gateway canary 23s),然后 swap 失败:

00:30 ssh-add "Could not open a connection to your authentication agent":macOS 私钥口令 + ssh-agent/钥匙串配置手册 » 笔记 by 车东

一次 SSH 密钥轮换(旧钥换新钥 + 加口令 + 全节点部署)踩坑后的沉淀,主机信息已隐去。

TLDR

ssh-add“Could not open a connection to your authentication agent”,九成是 SSH_AUTH_SOCK 没指向 launchd agent 的 socket——agent 本身在跑,只是 shell 找不到它:

# 找到 launchd agent 的 socket(路径每次重启会变)
launchctl print gui/$(id -u) | grep SSH_AUTH_SOCK

# 带上 socket 加载钥匙并存入钥匙串
SSH_AUTH_SOCK=<上一步的路径> ssh-add --apple-use-keychain ~/.ssh/id_ed25519_current

配套三件事缺一不可:

  1. 私钥设口令:ssh-keygen -p -f ~/.ssh/id_ed25519_current
  2. ~/.ssh/configHost * 段的 AddKeysToAgent yes + UseKeychain yes
  3. ~/.zshrc 加 socket 自动恢复段(见下文第 4 节),否则新终端里 pdsh 等批量工具全部认证失败

0. 原则

1. 给私钥加/改口令

ssh-keygen -p -f ~/.ssh/id_ed25519_current
# 输入旧口令(无则直接回车) → 输两次新口令

验证口令已生效(能无口令派生公钥说明还是裸钥):

Sun 13 September, 2026

09:00 Homebrew 停止 Intel 支持:迁移到 MacPorts 的完整笔记 » 笔记 by 车东

2026 年 9 月起,Homebrew 停止提供 Intel (x86_64) 的预编译 bottle。对 Intel Mac 用户意味着:任何没有预编译包的 formula,每次升级都要在本机源码编译。实测 rust 类程序在 8GiB 内存的机器上编译常报 SIGSEGV 栈溢出,llvm 等大依赖一次要编译很久。

替代方案 MacPorts 2.12.6 仍持续发布 x86_64 预编译二进制包。将重编译型软件迁到 MacPorts,brew 只留小工具,是一条可行的路。本文整理多台 Intel Mac 迁移的完整经验。

迁移决策:只迁有预编译 archive 的包

核心原则:MacPorts 有预编译 archive 才迁移;需要源码编译的保留 brew 版。另有一类根本不进系统包管理器——语言生态自带的官方工具链(Python 的 uv、Rust 的 rustup 等),见下文。

archive 探测(200 即迁,404 则保留):

curl -sI -o /dev/null -w "%{http_code}\n" \
  https://packages.macports.org/eza/eza-0.23.5_0.darwin_24.x86_64.tbz2

_0 是 port revision,可能为 _1/_2。404 不一定是没有:

端口命名与 brew 的差异

brew MacPorts 端口
python@3.14 python314
go go-1.27(无裸名端口)
httpd apache2
node@22 nodejs22
mysql-client@8.4 mysql9(等官方二进制)
imagemagick ImageMagick7
ffmpeg ffmpeg(+gpl2 variant 才有完整编解码)

前置准备

免密执行 port

批量迁移要全自动,给 port 开 NOPASSWD(只限这一条命令):

Tue 08 September, 2026

15:30 OpenClaw 2026.9.3 升级后 Gateway 无响应:SQLite canonical index no such column workspace_dir 修复 » 笔记 by 车东

openclaw update 升 2026.9.3 时 openclaw doctor 卡在状态库迁移:SQLite canonical index idx_skill_workshop_collection_reviews_workspace_time failed ... no such column: workspace_dir。更新恢复机制按设计保持 Gateway 停止,于是 Telegram / Feishu 全部无响应。根因是 2026.9.x 迁移代码只补了 claim_released_time 列、漏补 workspace_dir 列;把两张空的 skill_workshop 表删掉让 canonical DDL 重建即可。

TLDR

  1. 现象:升级 2026.9.3 失败,Gateway 起不来,通道无响应。日志(triage handoff)显示 reason: "runtime-verification-failed",doctor 停在 no such column: workspace_dir
  2. 根因(上游迁移 bug):2026.9.x 把 skill-workshop 的 skill_workshop_collection_reviews 表从 owner_agent_id 列改成 workspace_dir 列(proposals 表新增 workspace_dir NOT NULL + claim_released_time),但迁移代码 ensureSkillWorkshopSchema 只执行了 ensureColumn(..., "claim_released_time INTEGER")没有补 workspace_dir。随后 repairCanonicalSqliteIndexes 重建 canonical 索引 idx_..._workspace_time ON (workspace_dir, ...) 时列不存在 → 抛错 → doctor 拒绝继续 → 更新恢复保持 Gateway 停止。
  3. 修复:4 张 skill_workshop_*全部是空的,直接删表 + 删旧索引,让 canonical DDL(CREATE TABLE IF NOT EXISTS + 正确索引)按需重建。备份后零数据损失。
  4. 验证openclaw doctor EXIT 0 → openclaw gateway start → Telegram / Feishu running, connected → 重跑 openclaw update 顺利升到 2026.9.3。

现象

openclaw update 输出里 doctor 阶段失败,诊断手稿(~/.openclaw/logs/support/openclaw-triage-prompt-*.md)记录:

Tue 01 September, 2026

23:14 feat: cache-bust CSS/JS with version query + improve MCP search descr… » Recent Commits to phpman:master
10:15 OpenClaw 升级后 Telegram 无响应:修复 invalid persisted session row 与 Legacy exec approvals 迁移 » 笔记 by 车东
00:21 docs: CLAUDE.md 分支约定 — 明确 master 为主干,勿 push 其他分支名 » Recent Commits to phpman:master
00:16 docs: CLAUDE.md 分支约定 — default 计划统一为 main(过渡期仍 push master) » Recent Commits to phpman:master

Mon 31 August, 2026

00:22 fix: showForm 传入 markdown/json URL 修复 not-found 兜底链接失效 » Recent Commits to phpman:master
00:13 Codex 配置 GitHub MCP:解决 "Environment variable is not set" 报错 » 笔记 by 车东

Sun 30 August, 2026

21:11 Codex 0.151 弃用 `api_key`:taotoken provider 改用 `requires_openai_auth` » 笔记 by 车东

Thu 13 August, 2026

08:24 tools: 迁移 Tailscale mesh 工具至 chedong.com 仓库 » Recent Commits to phpman:master

Wed 12 August, 2026

23:22 tools: 发现阶段自动跳过跨 tailnet 共享节点 » Recent Commits to phpman:master
12:57 tools: mesh 带宽统一使用 Mb/s 单位(含 code-review 缺陷修复) » Recent Commits to phpman:master