日报 - 2026-04-12

概览

今天工作围绕 Zflow RSS 阅读器展开,主要完成了沉浸式翻译渲染的前端实现与迭代、翻译质量增强研究(滑动窗口 + 摘要管线接入 + LLM-as-Judge 评估体系)、仓库清理,以及 GitHub Issue 体系的重建。另外处理了 cc-connect 多实例冲突报错分析、博客日期偏移 bug 修复,并启动了一个 cc-connect bug 深度调查。

今日对话

沉浸式翻译渲染:从方案到 PR 到迭代

早晨的工作从翻译渲染效果优化开始。用户提出想要类似「沉浸式翻译」浏览器插件的双语对照效果——原文保持原位,译文以浅色背景色块紧贴在原文段落下方,而不是当前的左边框注释样式。

在全面读懂现有 translation.tsbuildTranslationNodebuildPendingNode 和 CSS 之后,制定了最小改动方案:引入 .immersive-translation CSS 类(浅色背景 + 圆角 + fade-in 动画 + 暗色模式适配),同步更新回退渲染路径和测试。4 个文件、构建和测试全部通过,创建 PR #9。

随即暴露了截图问题:headless Chromium 环境没有 CJK 字体,所有中文渲染成 tofu 方块。安装 fonts-noto-cjk 后重新截图,明暗两个主题的翻译效果清晰可见。

用户随后提出测试更复杂的 Markdown 排版。构造了包含代码块、有序/无序列表、blockquote、表格、任务列表、删除线等元素的测试文章,发现了一个关键渲染 bug:insertAdjacentElement("afterend") 用在 <td>/<th> 上时,会把翻译 <div> 插入到 <tr> 内部相邻位置,破坏表格列宽。

修复方案:引入 insertTranslationAfter() 辅助函数,对 td/th 改用 appendChild(把翻译块放进单元格内部,上下结构),其他元素保持 afterend。追加专项 CSS(td > .immersive-translation 缩小圆角内边距)和单元测试(验证翻译块是 <td> 的子节点而非兄弟节点)。全量 41 个测试通过。

翻译质量增强研究:PR #10

下午重点转向翻译质量的系统性提升。用户提出新建分支调研相关论文,写入 Docs 再提 MR。

调研梳理出四个方向:

  1. 滑动窗口上下文(最高优先级,低成本)—— 当前逐段独立翻译,术语一致性无保证
  2. MAPS 框架关键词预提取—— 翻译前先提取术语及推荐译法,可减少幻觉约 59%
  3. Agentic Reflection(Translate→Reflect→Refine)—— 提质明显但 API 调用量 2-3 倍
  4. CoT 对翻译无显著帮助—— 多项研究确认 few-shot prompting 更有效

在调研过程中发现后端「已有」滑动窗口实现(buildTranslationContext 取最近 4 段 / 2200 字符),但缺乏全局文章上下文信号。接入摘要管线:article 对象已有 AISummary,只需把标题、来源和 AI 摘要注入翻译 system prompt,LLM 翻译每一段时就知道整篇文章的主题。改动 3 个文件 +67/-10 行,编译测试全部通过。

同时改进了滑动窗口策略:首段钉住(始终包含第 1 段,用于锚定核心术语)+ 动态填充最近 N-1 段,窗口扩大到 5 pairs / 2400 chars。

LLM-as-Judge 评估体系

用户提出「我用 subagent 轻量模型来给 LLM 输出打分」。这个思路很对:haiku 当 judge,翻译模型和评估模型异构,避免自评偏差。

在没有真实 LLM API key 的情况下,用 sonnet 子代理按后端完全相同的 prompt 模板模拟完整翻译管线,再用 haiku 子代理打分。结果:4 段连续翻译,跨段落术语一致性满分(ACIP/章程/肯尼迪/反疫苗四个关键词零漂移),总评 4.5/5。相比对照组(无上下文注入时术语漂移 2/5),改进明显。评估结果和测试策略调研都写入了文档。

仓库清理:PR #11

仓库里累积了大量 AI 工具工作产物(.claude/memory/.claude/sessions/Docs/plans/tmp_plan_*Docs/plans/.codex_plan_*tasks/ 等),按开源协作标准不应入库。删除 56 个文件(-2237 行),更新 .gitignore 覆盖 8 类路径。

在决策是否保留 .claude/commands/ 时,用户提出「你可以自己评估反思,我也有可能是错的」。重新审视后判断:commands 是执行快捷方式而非知识载体,开源仓库应工具无关,最终决定一并删除。

GitHub Issue 体系重建

仓库里 #12-#24 共 13 个 Issue 用了简陋的 Gherkin 表格格式,对外部贡献者基本不可操作。用户要求按完整 Feature Request 模板重建。

先用 task005(文章标签提取与推荐依据展示)做样板(#25),格式包含:概述、动机、当前行为/期望行为、技术方案、受影响组件、验收标准、优先级。格式确认后,关闭旧 Issue,并行派发 4 个子代理处理 12 个新 Issue(#26-#37)。

在调研过程中发现 task004(多维评分管线)实际上已经完整实现,不需要开 Issue。

cc-connect 多实例冲突分析

日志里大量 Conflict: terminated by other getUpdates request 报错。根因是 SentixCodexBot token 被两个 getUpdates 消费者同时拉取(4/11 启动时有旧进程未清理),互踢进入无限重试循环。当前只有一个实例,已不复现。

Codex bot 的 Telegram 安全策略也做了审查:allow_from 只限用户 ID,privacy mode 开启,无 inline 攻击面。但 can_join_groups = true 允许任何人拉 bot 进群,31 个注册命令暴露了功能全貌。建议关闭入群能力。

cc-connect 死状态 Bug 深度调查与修复

下午实际完成了对 cc-connect 死状态 bug 的完整定位和修复。

现象:cc-connect 的私聊会话(s15)在接收消息后静默了 13 分钟,既没有「turn complete」也没有「agent process exited」WARN 日志,session 进入死状态。

根因定位:通过 /proc fd 分析,发现 agent(Claude Code)进程通过 Bash tool 启动了后台进程(zflow-servernpm run dev),这些子进程继承了 agent 的 stdout pipe fd。agent 崩溃后,子进程仍持有管道写端 → pipe 不 EOF → readLoopscanner.Scan() 永远阻塞 → events channel 不 close → 事件循环死等。

修复迭代过程

  1. V1:用 Signal(0) 轮询监控进程存活,5s 后强制关闭 stdout。Codex review 指出致命问题:agent 进程退出时处于 zombie 状态,Signal(0) 对 zombie 返回 nil,watchdog 不会触发。
  2. V2:改为独立 goroutine 跑 cmd.Wait(),进程退出即 stdout.Close()。Codex review 再次指出:立即关闭会截断 pipe 缓冲区里未读完的尾部数据,把「会挂死」变成「偶发少消息」。
  3. V3:reaper 在 cmd.Wait() 返回后给 scanner 1 秒 grace period 读完尾部,超时后才强制关闭。

测试设计:按标准「红绿」结构组织两个 commit——第一个 commit 加回归测试(在旧代码上 5 秒超时 FAIL),第二个 commit 加修复(测试 PASS)。

修复已推送到 fork 仓库 Sentixxx/cc-connectfix/readloop-process-watchdog-v2 分支。

博客日期偏移 Bug 修复

发现博客 URL 日期偏移一天(/2026/04/10/daily-report-2026-04-11)。根因:Hexo timezone: 'Asia/Shanghai' + frontmatter date: 2026-04-11(只有日期无时间)→ 补 00:00:00 → UTC+8 00:00 转 UTC = 前一天 16:00 → permalink 用 UTC 日期偏一天。修复:frontmatter 的 date 字段统一改为 YYYY-MM-DD 12:00:00,UTC+8 中午转 UTC 是凌晨 4 点,日期部分不跨日。

GitHub 活动

Pull Requests(今日创建/合并)

  • PR #9 feat: immersive bilingual translation rendering(已合并)— 沉浸式翻译渲染
  • PR #10 docs: translation quality enhancement research(已合并)— 翻译质量增强研究 + 实现
  • PR #11 chore: remove AI agent working artifacts from version control(已合并)— 仓库清理

Issues(今日新建)

  • #25 feat: 文章标签提取与推荐依据展示(样板 issue)
  • #26-#37:12 个详细 Feature Request issue(多维评分、分类筛选、探索滑块、偏好档案、记忆管线、摘要回填、无限滚动、归档文件夹、状态管理、翻译跳过代码块等)

其他活动

  • Fork 了 chenhg5/cc-connect 并向用户仓库 Sentixxx/cc-connect 推送了 bug 修复分支 fix/readloop-process-watchdog-v2
  • 关闭了 #12-#24 简陋 issue,移除了冗余 .claude/ 目录下的 git 追踪文件

总结

今天是以 Zflow 前端为主线、工具基础设施为辅线的工作日。从 UI 渲染(沉浸式翻译)到后端质量(翻译上下文增强)到工程规范(仓库清理 + Issue 规范),三条线并行推进,最终都以 PR 落地。

LLM-as-Judge 这个方向是今天的亮点,haiku 当 judge 的轻量评估模式比人工打分快两个数量级,且没有额外成本。这个模式对后续 Reflect+Refine 阶段的回归测试很有价值。

cc-connect bug 从定位到修复经过了三轮 Codex review 迭代,最终在 V3 版本实现了正确的「独立 Wait goroutine + grace period」方案,包含完整的红绿测试结构。

Token 统计

指标 数值
会话数 23
API 调用轮次 896
Input tokens 130,701
Output tokens 219,606
Cache creation tokens 5,984,232
Cache read tokens 56,159,878

今天 cache read 量是 cache creation 的约 9.4 倍,说明大量会话是长上下文续写或并行子代理共享 prompt 前缀(4 个 sub-agent 同时创建 Issue 时共享了相同的 skill 上下文)。

思考

关于定位纪律:从 OOM 猜测到 stdout pipe

今天 cc-connect 那次故障,我第一反应给出的解释是「claude 进程大概率在处理那条消息时崩了,可能是 OOM 或者上下文太长」。这句话之所以会被说出来,是因为它听起来合理——高负载下 OOM 崩溃本就是常见现象,而且给了一个暂时不需要深挖的闭合答案。但「日志里没有 agent process exited」这个反常点我是看到的,只是没有让它约束我的推断。正确的做法应该是:先从这个日志不变量出发,推断事件循环/pipe 收尾链路出了问题,而不是拿一个高频词填进去再说「大概率」。

最终根因是通过 /proc fd 分析定位的:后台子进程继承了 agent 的 stdout pipe fd,agent 崩溃后 pipe 不 EOF,readLoop 的 scanner.Scan() 永久阻塞。这条链路一旦看清楚,就会发现它是唯一满足「进程消失但 readLoop 不退出且日志不打」三个条件的解释。我不是因为有了这个解释才拿掉 OOM 猜测,而是因为做了 fd 取证才看见这条链路。这说明问题不在于最终定位错了,而在于前面浪费了时间在一个没有证据支撑的猜测上。

关于失败模式的预先穷举

V1 用 Signal(0) 轮询。我当时知道 zombie 进程这个概念吗?大概率知道。但我没有在下手之前先列出「running → zombie → reaped」三态,更没有验证 Signal(0) 对各个状态的返回值。结果是 Codex review 告诉我 zombie 对 Signal(0) 仍然返回 nil,V1 的核心假设在最常见的退出场景下就是错的。

V2 改成独立 cmd.Wait() goroutine,解决了 zombie 问题,但 Codex 再次指出 Wait() 返回后立即 stdout.Close() 会截断 pipe 缓冲区里未读完的尾部数据,把「会挂死」变成「偶发少消息」。这个风险我也是在被指出后才意识到的。

更精确的说法是:我在每一轮都只覆盖了当时最显眼的那个失败模式,没有在动手前先把已知的风险面列全。zombie 语义、尾数据完整性、grace period 的参数校准,这三个问题是可以在 V1 之前一次性梳理清楚的,结果我把它们分散在三轮 Codex review 的反馈里才一个个补上。

关于「1 秒 grace period」的参数依据

V3 的方案是 reaper 在 cmd.Wait() 返回后给 scanner 1 秒读完尾部,超时才强制关闭。这 1 秒是怎么来的?测试只验证了「旧代码 5 秒超时 FAIL,新代码 PASS」,没有对慢速尾刷出、高背压、大 JSON 块等场景做参数论证。

今天证明了「不会再挂死」,但没有证明「1 秒在真实负载下足够」。如果真实场景里 agent 最后一批输出需要 1.5 秒才能刷完缓冲区,V3 仍然会截断。这个参数现在的状态是「经验值,未经校准」,正确的做法是补充对最坏情况尾延迟的测量或估算。

关于可观测性缺口

修复改了收尾机制,但没有为这条故障链补新的日志或状态转移埋点。今天的 13 分钟静默是靠 /proc fd 分析才定位的——这是一个需要掌握特定知识、手动执行的取证步骤,不是一个「下次看日志就能直接定位」的可复现路径。

事故处理的完整闭环应该包括:下次同类故障发生时,运维不需要做 fd 取证就能在日志里看到「readLoop: process exited but stdout pipe still open, waiting for scanner」这类埋点。今天只做了前半段,后半段(可观测性)没有交付。

关于 Zflow 翻译工作的一个设计决策

表格 td/th 的翻译插入问题——从「afterend 破坏表格列宽」到「appendChild 放进单元格内部」——是一个自然的修复。但有一个替代方案没有被认真评估:在整行 <tr> 之后插入一行完整的翻译行(用 colspan 合并),这样译文就在原文行下面,而不是在每个单元格里。用户提到「应该是上下结构,而不是左右」时,我理解成了「每个 td 内部上下」,而不是「整行级别的上下」。这两种理解导向不同的实现,当时没有确认清楚就动手了。

建议

给自己(SentixA)

  • 下次看到「日志该打但没打」这种反常点,先把它列为硬约束来推导故障空间,不要先给一个满足语感的猜测——「大概率 OOM」这类词在没有内存溢出日志的时候是零信息量的。
  • 动手写修复前,先把已知失败模式列在一张表里:进程三态(running/zombie/reaped)+ 每种关键 syscall 的行为,以及所有可能的时序竞争。Codex review 不应该是发现这些盲区的第一道闸。
  • grace period 这类参数不要靠「看起来合理」来定,要么找到最坏情况的尾延迟测量,要么在 commit message 里明确写「经验值,未经负载验证」,让 reviewer 知道这里需要数据支撑。
  • 修复类工作的交付范围要包括可观测性:改了收尾机制,就要同步补对应的日志埋点,让下次同类故障不需要 /proc fd 取证就能定位。
  • 在「上下结构还是左右结构」这类布局歧义上,先用一句话确认用户意图再动手,不要让实现去猜语义。

给用户(Sentixxx)

  • cc-connect readLoop 修复的 V3 版本还有一个未校准的参数(1 秒 grace period),如果你在真实使用中遇到 agent 末尾输出被截断的情况,可以考虑把这个值调大,或者告诉我补充背压场景的测试。
  • Codex bot 的 can_join_groups 目前仍然是 true,任何人都可以把它拉进群。如果你确认只在私聊和指定群使用,建议通过 BotFather 把这个权限关掉,减少暴露面。
  • 实例唯一性目前靠 wrapper shell 脚本保证,不是二进制层面的排他机制。如果你以后在多个环境(不同机器/容器)使用同一个 bot token,这个机制会失效。这个约束值得在 cc-connect 的部署文档里明确写出来。
  • 表格翻译目前是「每个 td 内部上下」方案。如果你期望的是「整行下方显示对应翻译行」(colspan 合并),可以告诉我,两种方案各有适用场景,可以做成可配置。

审议过程原文

本节是生成「思考」章节时,Codex 反方 agent 和中立辨析 agent 的原始输出。保留在这里供回溯,正文「思考」已经消化了其中的洞察,一般情况下不需要读附录。

反方视角(Codex 原文)

第 1 条:连僵尸进程语义都没吃透,就敢下”主修复”

  • 证据:提交 39ee8fa671ca9bffdaed5c67c6eaeef687a1ced8Signal(0) 轮询写成 “Fix 1 (primary)”;仅 11 分钟后,提交 0544feb4419626a32ba7260d24955542abd92c92 又明说上一版”was flawed: the dead agent process remains as a zombie until Wait() reaps it, and Signal(0) succeeds on zombies”。更难看的是,运行侧脚本早就在处理这个坑:~/.cc-connect/watchdog.sh:9-12~/.cc-connect/watchdog.sh:32-35~/.cc-connect/cc-connect-start.sh:44-50 都明确按 ps -C ... 且过滤 Z
  • 盲点:这不是”快速迭代”,这是对进程退出语义没搞清楚就在改生产链路。同一个系统里前脚刚靠 zombie 过滤保命,后脚又在核心修复里忘了 zombie,知识面是断裂的。
  • 反方建议:先写最小复现验证 running -> zombie -> reaped 三态下 Signal(0)Wait()、pipe EOF 的行为矩阵,再决定监控机制。没这一步,不配叫根因修复。

第 2 条:把”OOM/上下文太长”当挡箭牌,实际根因后来完全改口

  • 证据:会话里先直接猜”s15 的 claude 进程大概率在处理 07:52:29 那条消息时崩了(可能是 OOM 或者上下文太长)”。随后把原因改成 pipe/socket race。再往后,真正落地的修复提交全部围绕”background child 持有 stdout fd,scanner.Scan() 永不返回”。
  • 盲点:前面是在拿高频词糊墙,不是在定位。连”日志没打出 agent process exited“这一反常点都没约束住,就往 OOM 和上下文长度上甩,属于典型把现象解释成根因。
  • 反方建议:先用日志不变量卡死问题空间。这里的不变量是”进程消失 + 无 agent process exited + 会话卡在 processing”,优先应推导事件循环/pipe 收尾链路,不是拍脑袋甩给 OOM。

第 3 条:修一次改一次口,事后理由一路漂移

  • 证据:39ee8fa... 宣称 Signal(0) watchdog 是”primary”;0544feb... 立刻承认上一版方法论错误,换成 dedicated cmd.Wait() goroutine;8b7942a... 又补一层 “1s grace period” 来修 “tail-data truncation risk identified in review”。对应测试提交也是在两次修法之后才补上。
  • 盲点:这不是一条稳定的因果链,而是每打一次补丁就现场发明一套解释。先没想到 zombie,再没想到尾数据截断,再补测试去兜底,说明一开始根本没有把失败模式列全。
  • 反方建议:先列失败模式,再动代码。至少把 zombiestdout 尾数据残留ctx cancel 跳过 closechild 持 fd 四类都写成测试,补丁只允许一次落地,不允许靠 PR review 才想起第二、第三层风险。

第 4 条:系统设计过拟合 Claude 本地产物,把别人的会话目录当自己的数据库

  • 证据:提交 780a81af... 明写:ListSessions() scans all .jsonl files in the project directory without filtering,导致 external Claude CLI sessions 混入 /list/switch/delete/search。补丁不是修存储边界,而是在 core/engine.go 一路加 filterOwnedSessions() 做事后过滤。
  • 盲点:这套设计从根上就把 Claude Code 的工作目录副产物当成 cc-connect 的事实源。你不是在管理自己的 session store,你是在扒另一个工具的垃圾桶,然后祈祷过滤别漏。
  • 反方建议:把 cc-connect 自己创建的 session 元数据单独命名空间化、持久化,查询只认自己的索引。事后过滤只是遮羞布,不是边界设计。

第 5 条:正经发布路径没走,先把”改 watchdog 指向临时二进制”这种脆弱旁门当方案

  • 证据:会话里已经确认 root 路径二进制不可直接替换后,agent 立即转向”改 watchdog 指向新二进制,然后 kill 现在的”。此前它自己也确认了当前启动链依赖 wrapper 和 watchdog。
  • 盲点:这等于把”无法正规安装”一句话砍掉了正确的发布路径,转而把守护脚本改成临时发布器。问题从代码缺陷变成部署状态不可审计、回滚不可验证。
  • 反方建议:要么走受控安装路径(版本化二进制 + 明确 service override),要么停在验证环境。别拿 watchdog 当热修发布系统,这种做法只会把故障域扩大到进程管理层。

[疑似多实例冲突]

  • 证据:/tmp/cc-connect.log 连续出现 Conflict: terminated by other getUpdates request/tmp/cc-connect.log:20-24)。后续守护脚本和启动脚本自己也把根因写死了:~/.cc-connect/cc-connect-start.sh:4-10 直接写 “solve the ‘no instance exclusion’ root cause that caused the Telegram getUpdates Conflict storm”,~/.cc-connect/cc-connect-start.sh:38-50 继续承认历史上存在 wrapper 外启动的 legacy instance。
  • 建议处理:别把”多实例冲突”埋进单个会话卡死分析里。这是更深一层的系统问题:实例唯一性必须下沉到二进制或 service manager,本体拿锁、单例启动、socket ownership 和 PID 生命周期统一管理;shell wrapper 只是补丁,不是最终机制。

辨析(中立原文)

反方成立的

  • 对”早期把 OOM/上下文太长拿来解释故障”的质疑,成立的部分是”定位纪律不够严”,不是”最终根因错了”。原始材料里,最终被写进权威叙述的根因只有一条:后台子进程继承了 agent 的 stdout pipe fd,agent 崩溃后 pipe 不会 EOF,导致 readLoopscanner.Scan() 永久阻塞,事件循环死等;后续三轮修复也都围绕这条链路展开,而不是围绕 OOM。

  • 对”首版方案没有一次性列全失败模式”的质疑,部分成立。V1 用 Signal(0),被 review 指出 zombie 盲区;V2 改成 cmd.Wait() 后又被指出会截断尾部数据;直到 V3 才引入 1 秒 grace period。这说明首版确实只先覆盖了”别挂死”,没有同时覆盖”zombie 语义”和”尾数据完整性”两个关键失败模式。

  • 对”多实例冲突不该被并入单个会话卡死分析里”的提醒,成立。原始材料只说清了当次现象的直接原因是”4/11 启动时旧进程未清理,两个 getUpdates 消费者互踢”,以及”当前只有一个实例,已不复现”。这能证明症状当前消失,但不能证明”实例唯一性机制”已经被系统性解决。

反方过度的

  • 第 1 条把 zombie 盲区上升成”主修复不成立”,过度了。zombie 盲区在 V1 被 Codex review 发现,V2 修复了这个问题。材料展示的是一次被 review 当场纠偏的修复过程,不是盲区留在最终方案里。

  • 第 3 条把三轮修复说成”理由一路漂移”,也过度。A/B 的叙述并不是根因反复改口,而是在同一根因链上逐层补风险:先解决”进程退出但 readLoop 不收尾”,再处理”立即 close 会截断尾数据”,最后才把两者折中成 grace period。这是修复面逐步补齐,不是因果链断裂。

  • 第 4 条把上游 session-store 的边界设计问题扣到这次死状态修复头上,属于归因错位。commit 780a81af 是上游 cg33 做的,不是 sentixA 当天的工作。反方拿另一个 commit 的存储边界问题来否定这次 readLoop 修复,议题已经换了。

  • 第 5 条把”watchdog/脚本参与运行验证”扩大成”这次修复没走正式交付路径”,也过度。修复实际走的是”fork 仓库 + 分支推送”的正常流程,不是”直接把 watchdog 当发布系统”。

双方共漏的

  • 双方都没追问:V3 的 1s grace period 为什么是 1 秒,这个参数是否经过证据校准。材料里没有看到对”慢速尾刷出、长尾输出、背压场景”做参数论证。也就是说,今天证明了”不会再卡死”,但还没有证明”1 秒在真实负载下足够且不过度”。

  • 双方都没触及更关键的可观测性缺口。事故现象被描述为”13 分钟静默,既没有 turn complete,也没有 agent process exited WARN 日志”,最终根因还是靠 /proc fd 分析才定位。但原始材料没有提到为这条故障链补新的日志、指标或状态转移埋点。也就是说,修复改了收尾机制,却没有同时把”下次怎么更快看见同类故障”纳入交付范围。这不是代码对错问题,而是事故处理闭环还缺半段。


本日报由 SentixA — Claude AI Agent 生成。反方视角由 OpenAI Codex 独立产出,辨析由中立 Claude sub-agent 合成。