Skip to content

daemon: agent 测试运行继承生产 DB 路径变量,可整库清空引擎数据库(bc#853 停摆实锤) #7

Description

@ranxianglei

事故链(2026-09-17 01:17-01:35Z)

agent 按 issue 验收标准在 workdir 里跑 bun test → 测试进程继承了 daemon spawn 的 DAEMON_DB_PATH(config.ts:132 WORK_DB_PATH ?? DAEMON_DB_PATH ?? fallback)→ 测试 harness 的 beforeEach DELETE FROM messages/op_sessions/issues/daemons 清空了生产引擎库(250 issue 行、全部会话/消息状态)。后果:① 存量线程 comment_created → claimIssue(op.ts:515)FK constraint failed → handler 崩溃、线程永久不调度(bc#853);② 全部进行中任务丢失。已人工恢复:热修 ENV_DENY(线上树)、重启、按 web 库回填 250 行、requeue。

修复规格

  1. 运行时隔离(根治):runtime/opencode-backend.ts ENV_DENY_ALWAYS 增加 DAEMON_DB_PATH、WORK_DB_PATH、OPENCODE_DB_PATH、DAEMON_ENV(已在生产热修,需入库+测试:spawn 后子进程 env 不含这些键)
  2. 测试自隔离(纵深):所有 daemon 测试 harness 的 initDB 必须显式指向 tmp 路径,不读环境(或 tests/setup 统一 unset 上述变量+断言)——否则任何开发者环境带变量都会炸自己的库
  3. claimIssue 自愈:ensureOwned/claimIssue 前先 ensureIssue(upsert),FK 失败自动补行重试一次,杜绝「丢一行=线程死寂」
  4. web→daemon 对账器(可选加固):daemon 周期从 web 拉取 open issues,缺失行自动补(顺带覆盖本次回填的手工操作)

验收

四项各带测试;全量 bun test + tsc;PR 引用 Closes #N。注意:本 issue 的测试编写本身就要在隔离环境跑,别重蹈覆辙。


Mirrored from ework issue #7

Activity

  1. ranxianglei commented on Sep 17, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework

    [bot] 🏷 已接手。先给审核结论(动手前的验证):

    复现/查证:事故链与代码完全对得上——

    • packages/daemon/src/db.ts:78:DB_PATH = WORK_DB_PATH || DAEMON_DB_PATH || ~/.local/share/...,模块加载期读环境;config.ts readDbSection 同样 WORK_DB_PATH ?? DAEMON_DB_PATH ?? fallback。测试进程继承 daemon spawn 的 env 后,initDB() 直接打开生产库。
    • 更隐蔽的一点:packages/daemon/bunfig.toml 的 [test] preload=["./tests/setup.ts"] 只在包目录生效,monorepo 根没有 bunfig.toml——从根跑 bun test 时 setup 完全不执行,事故向量至今还在。且 setup.ts 只钉了 WORK_DB_PATH,没 unset DAEMON_DB_PATH/OPENCODE_DB_PATH/DAEMON_ENV(DAEMON_ENV 还会把 config 拉进 production 分支)。
    • bc#853「线程永久不调度」的机制也查实了:issues.owner_daemon_id REFERENCES daemons(id);daemons 行被清后,claimIssue 的 UPDATE 写悬空外键 → FK constraint failed → handler 崩溃。而且指向已删除 daemons 行的 owner 引用是死锁态:claimIssue 要求 owner IS NULL,releaseDeadOwners 只匹配"存在但心跳过期"的行——悬空引用谁都接不走,即使重启+回填,旧进程持有旧 daemonId 期间该线程也永远 claim 不到。

    层次判断:用户给的修复规格打到了核心。ENV_DENY 是根治(运行时隔离),测试自隔离是纵深,claimIssue 自愈是兜底,三者互补不冲突。我补充两点会一并做:① releaseDanglingOwners()(清悬空 owner 引用)——这是"丢一行=线程死寂"的另一半,规格里没点名但不加的话自愈不完整;② ENV_DENY 同时覆盖 pi-backend.ts(它有同样的 env 直通,只是没有 always-deny 列表)。

    方案:四项全做(web→daemon 对账器不在"可选"范围外,一并实现,用 web shim 已验证的 /api/v1/repos/issues/search?state=open 端点),各带测试;全量 bun test(包内+仓库根两条路径都验)+ tsc;PR 引用 Closes #7。测试本身在隔离环境跑(setup 统一 unset+断言)。

    开始实现,有进展再更新。

  2. ranxianglei commented on Sep 17, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    完成,PR 已开:#10 #7)

    审核结论(动手前已验证)

    • 事故链复现成立:config.ts/db.ts 的取值顺序确为 WORK_DB_PATH ?? DAEMON_DB_PATH ?? 默认路径,daemon spawn 环境原样透传子进程(ENV_DENY_ALWAYS 原本只挡 OPENCODE* 键),测试 harness beforeEach 对 messages/op_sessions/issues 全表 DELETE —— 只要进程环境带生产库变量,跑 bun test 必清生产库。
    • bc#853 线程死寂是表面现象,根因分两层:① 库被清后 claimIssue 的 UPDATE 触发 FK 失败;② 更隐蔽的一层——releaseDeadOwners 的 WHERE 子句要求 daemons 行存在,永远看不到「owner_daemon_id 指向已删除行」的悬空引用,这类 issue 永久无人认领。修复按三层走:断继承(根治)、自愈(纵深)、对账(兜底)。

    规格四项全部落地,各带测试

    1. 运行时隔离:ENV_DENY_ALWAYS 增加 DAEMON_DB_PATH、WORK_DB_PATH、OPENCODE_DB_PATH、DAEMON_ENV,opencode 与 pi 两个 backend 共享同一常量(pi-backend 存在同样的泄漏,一并修)。测试 spawn 假二进制 dump 自身 env,断言四键不存在。
    2. 测试自隔离:tests/setup.ts 统一 unset 上述变量(含 WORK_DB_PREFIX)并把 WORK_DB_PATH 钉到 os.tmpdir() 下唯一文件(pid+uuid;bun 并行文件间 pid 会复用,单 pid 会撞库——本次实测撞过)。新增根级 bunfig.toml,从仓库根裸跑 bun test 也不再可能碰到生产/默认路径。另有子进程回归模拟测试:继承一个恶意 DAEMON_DB_PATH 起子进程,证明测试库不会被带偏。
    3. claimIssue 自愈:ensureOwned 先确保 issue 行存在(注意:重建的行拿到全新本地 uid,下游一律用返回的行,这是实测踩出的坑);FK 失败时 healDaemonRow 重新注册 daemon 身份并重试一次。另加 Store.releaseDanglingOwners() 清理指向已删 daemons 行的悬空 owner(启动+观察周期两处调用)。
    4. web→daemon 对账器:观察周期每小时(可触达或零 owned issue 时)查 web 的 open issues 搜索接口,缺失行自动补为 active 且不触发任务;scopes = 现有 active issue 的 scope 并集 + 新增配置 WORK_RECONCILE_SCOPES(work.reconcileScopes)。顺带覆盖本次手工回填的操作。

    验证

    • tsc --noEmit 干净;23 个新测试在包目录、仓库根、敌对环境子进程三种上下文全绿。
    • 全量 daemon 套件 228/258,30 个失败与本沙箱 master 基线逐条 diff 完全一致(纯环境因素:~8 个旧 harness 硬编码 /tmp 路径在只读 /tmp 挂载下 EACCES;loopback clone 走 egress proxy 返回 503)——零回归。

    附带发现(按规则只在本线程报告,不另开 issue)

    1. ~8 个既有测试 harness(multi-engine、exit-chain、concurrency、model-pool、session-capture、stranded-interrupted、pi-backend、workdir)硬编码 /tmp/... 路径,任何只读 /tmp 的环境都会 EACCES,建议统一改 tmpdir()。
    2. 跨包环境变量冲突:web 与 daemon 都从同一个 WORK_DB_PATH 解析各自的 DB 但 schema 不同;从仓库根混跑两包测试时文件共享同一进程环境(bun 忽略嵌套 bunfig)→ 会出现「no such column」类串扰。属既有地雷,本 PR 未动。

    一句话总结:根治了 agent/测试进程继承生产 DB 路径变量导致的整库清空(运行时剥变量+测试钉 tmpdir+根级 preload 三道),并为「丢一行=线程死寂」加了自愈、悬空清理、小时级 web 对账三层防线,验证零回归,可以合并。

  3. ranxianglei commented on Sep 17, 2026

    @ranxianglei
    OwnerAuthor

    继续

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions