fix(cli-runtime-update): 用 canonical realpath 作持久化身份,修复 FNM 旋转路径下 Codex 更新每小时重复提醒 - #891
Conversation
d0f9ee1 to
09d34df
Compare
|
自动评审初步意见:canonical realpath 作为持久化身份的主方向正确,现有 42 个模块测试和全量 阻断项:migrate 只校验 orphan 唯一,没有校验 live target 唯一。
实测构造“1 个已删除的旧安装 orphan + 2 个仍在线、同 runtime/source 的真实安装”后,只有第二个安装执行了 probe/notify;第一个安装随机继承旧 建议 migration 同时要求该 group 的 configured live targets 也恰好只有一个;只要 target 或 orphan 任一侧不唯一,就不要迁移,prune 模糊 orphan 后让各安装独立 probe,并补一个上述双安装回归用例。 另有两个小项建议顺手修正:
|
…ex 更新每小时重复提醒 FNM hands each new login/interactive shell a fresh /run/user/.../fnm_multishells/<pid>_.../bin/codex launcher symlink, all resolving to one npm install. The update monitor persisted entries under `runtimeId + raw binPath`, so every hourly tick minted a new key, pruned the old one, and lost its 24h TTL and per-version notification watermark — re-probing and re-notifying the owner every hour for an unchanged latest. Persist a stable identity instead: - Add `installationPath` (canonical realpath captured at write time) to the entry and key the store by `runtimeId + installationPath`. The raw `binPath` is retained as a display-only field. installationPath is frozen because a rotated launcher symlink is already dead by the next read and cannot be re-resolved. - `canonicalInstallationPath()` (realpath, resolve() fallback) backs targetKey, entryKey, dedupe and the dashboard filter, so rotating symlinks collapse onto one row. - Reconcile before any TTL decision: reindex entries whose stored path still resolves onto their canonical key (carries watermark for the alive-symlink upgrade case), and migrate a *stale* orphan (installationPath no longer on disk) onto a live target only when both sides are unambiguous — exactly one orphan AND exactly one live install lacking an entry share the group. When a runtimeId+sourceFingerprint hosts two distinct live installs, migrating by iteration order would graft a stale watermark onto an arbitrary install and suppress its next probe, so migrate none and let each install re-probe under its own identity. A genuinely different install (realpath changed, old file still present) is a new identity: re-probed, never inheriting the old watermark. - validEntry derives installationPath for pre-fix stores from the persisted binPath. - Group key uses a `\u0000` source escape (not a literal NUL byte) so the file stays text-tool friendly. Regression tests use real symlinks to exercise actual realpath rotation and cover acceptance criteria 1-4, the alive-symlink reindex and dead-path migration upgrade boundaries, plus the two-live-install ambiguous group where no migration may occur. Co-Authored-By: Claude <noreply@anthropic.com>
09d34df to
651232f
Compare
问题
botmux 在 FNM 环境下 Codex 更新提醒每小时重复发送:版本差检测正确,但同一个 latest 每小时都通知一次。
根因
cli-runtime-update持久化 key =runtimeId + raw binPath。FNM 每个新 login/interactive shell 生成一个新的/run/user/.../fnm_multishells/<pid>_.../bin/codex临时软链,resolveCommand('codex')每小时拿到不同 raw path → 新 key;旧 key 不在 configuredKeys 里被 prune 删除;新 entry 丢失lastCheckedAt/lastNotifiedVersion→ 24h TTL 和「同版本只提醒一次」水位双双失效 → 每小时 probe + notify。这些路径最终都指向同一个 npm 安装。改动(src/core/cli-runtime-update.ts)
runtimeId + canonical realpath;raw binPath 降级为纯展示字段。installationPath(写入时冻结的 realpath)——旋转软链下次读取已失效无法再解析,故身份必须落盘;validEntry对旧行从 binPath 兜底推导,无回归。canonicalInstallationPath()(realpath 失败回退 resolve),targetKey/entryKey/dedupe/filter 全部收敛到同一 identity,旋转软链自然折叠到同一行。影响面
仅改 host 侧 Codex 更新监控(
core/cli-runtime-update),read-only 探测语义不变,不触碰其它 CLI/后端/IM 路径。测试
cli-runtime-update42 passed;tsc 全量 emit 通过。