Repository navigation
fix(daemon): isolate agent/test processes from engine DB and self-heal ownership - #10
ranxianglei wants to merge 10 commits into
Conversation
ENV_DENY_ALWAYS gains DAEMON_DB_PATH, WORK_DB_PATH, OPENCODE_DB_PATH and DAEMON_ENV, shared by the opencode and pi backends via runtime/env-deny.ts. Agent-spawned processes (and any test run they launch) can no longer open the engine database through inherited environment. #7 item 1.
tests/setup.ts now scrubs DAEMON_DB_PATH, WORK_DB_PATH, OPENCODE_DB_PATH, DAEMON_ENV and WORK_DB_PREFIX and pins WORK_DB_PATH to a unique file under os.tmpdir() (pid alone collides under bun pid recycling across parallel files). A root-level bunfig.toml wires the same preload so bare bun test from the repo root can no longer reach production or default DB paths. db.ts exports RESOLVED_DB_PATH for assertions; a child-process regression sim proves an inherited DAEMON_DB_PATH cannot redirect the test DB. #7 item 2.
WORK_RECONCILE_SCOPES comma-separated list, defaulting to empty; existing test Config literals gain the new required property. Prep for the web reconciler.
ensureOwned now re-creates a vanished issue row before claiming (a recreated row gets a fresh local uid, so all downstream references use the returned row) and, when the claim hits a foreign-key failure, re-registers the daemon identity via healDaemonRow and retries the claim once. Store gains releaseDanglingOwners() for issues pointing at deleted daemons rows - a case releaseDeadOwners can never see because its WHERE clause requires the daemons row to exist - wired at boot and in the observer cycle. Tests cover FK heal, row recreation, dangling-owner release and the lost-claim race. #7 item 3.
runObserverCycle hourly (when the web endpoint is reachable or the engine owns no issues) queries the gitea shim issues search endpoint for open issues in tracked scopes - distinct scopes of active issues plus work.reconcileScopes - and backfills missing rows as active without spawning work. Automates the manual backfill performed during the bc#853 incident. #7 item 4.
…l open issues The web shim's issue search endpoint only supported a limit (capped at 200) with no offset or page parameter, so the hourly web->daemon reconciler could only ever see the most recently updated open issues up to its limit. On instances holding more open issues than that cap, missing rows beyond it would never be restored by the self-heal path added in this PR. - store.ts: listAllIssues accepts an optional offset (clamped >= 0) and appends OFFSET to its SQL; listIssues is unchanged. - giteaApi.ts: the search handler accepts a 1-based page parameter (default 1) translated to an offset; requests without page behave exactly as before. - web-reconcile.ts: default limit raised 100 -> 200; walks pages until a short page, dedupes across shifting pages via a seen set, stops when a stale shim repeats the same page, and caps the walk at maxPages (default 25). - Tests: multi-page walk, stale-shim repeat guard, maxPages cap, and store-level offset slicing/clamping.
[bot] 🏷 Review complete — found and fixed one real defect, pushed to this branch ( Verdict on the four spec items: all implemented correctly. Verified line-by-line against The defect (now fixed): the web→daemon reconciler made a single search call with Fix (commit
Verification:
I did not merge — that stays with you. Once you're happy, please merge via GitHub. 中文摘要:审查确认四项修复均正确实现且 CI 全绿;发现并直接修掉一个真实缺陷——web 侧 issue 搜索接口不支持分页(仅 limit≤200),导致每小时对账最多只能看到最近更新的 100 条 open issue,超出上限的丢失行永远无法自愈(实测该实例 open issue 已 ≥200 条);已在本分支补上端到端分页(store offset、接口 page 参数、reconciler 翻页+去重+防旧版 shim 死循环+页数上限)并加双侧测试,类型检查与全部测试通过、零回归,可以合并。 |
The five live-server test harnesses spawn the real web server with stdout/stderr ignored, so a failed boot surfaced as a bare 20s healthz timeout with zero diagnostics (csp.test.ts failed this way in CI on three consecutive runs, on commits both before and after this PR's changes). Capture the child's stderr to a per-process scratch file and append its tail to the timeout error; no behavior change otherwise.
tests/setup.ts pinned WORK_DB_PATH to /tmp/ework-test-<pid>.db. Bun recycles pids across parallel test files, so two files can pin the same DB file; when both spawn the real server, one server's boot migration then fails with SQLITE_BUSY inside the other's file and the healthz wait times out with no diagnostics (reproduced in CI: csp.test.ts boot crash at migrateUsersTable). The daemon-side setup in this PR already uses pid+uuid for exactly this reason; bring the web preload to the same guarantee.
[bot] 🏷 CI follow-up — root cause of the red build found and fixed The re-run confirmed the picture: the Two follow-up commits on the branch:
Both commits typecheck clean; my pagination tests were already passing in Docker CI ( 中文摘要:定位并修复了 CI 反复失败的根因——web 测试预加载用纯 pid 命名测试库,bun 并行测试间 pid 复用导致两个测试文件共用同一个 SQLite 文件、启动迁移互相锁死(SQLITE_BUSY),已按本 PR daemon 侧同样的 pid+uuid 方案修复,并给 5 个起真实服务的测试补了 stderr 诊断;等新一轮 CI 结果确认后回复。 |
[bot] 🏷 CI green — PR ready for human review/merge The run on Final branch shape — original 5 commits plus three follow-ups from review:
Findings reported here only (no code changes, per protocol):
PR state: open, mergeable/clean, CI green. I did not merge — that stays with you. 中文摘要:CI 已全部转绿(web 202/0、daemon 261/0、aio 19/0,csp 与分页测试均通过);分支在原有 5 个提交上补了 3 个 review 发现(分页缺口修复、测试 stderr 诊断、web 测试库路径加 uuid 根治反复红 CI);另报告了 3 个遗留观察项(其他包同类 pid 命名隐患、共享测试库的良性锁日志噪音、5 个测试服务器端口窗口重叠),均只报告不改码;PR 可合并,合并动作留给你。 |
|
冲突 |
…solation-claim-heal # Conflicts: # packages/daemon/src/config.ts # packages/daemon/tests/concurrency.test.ts # packages/daemon/tests/exit-chain.test.ts # packages/daemon/tests/handle-closed.test.ts # packages/daemon/tests/model-pool.test.ts # packages/daemon/tests/multi-engine.test.ts # packages/daemon/tests/session-capture.test.ts # packages/daemon/tests/setup.ts # packages/daemon/tests/stranded-interrupted.test.ts # packages/web/tests/setup.ts
master added infraRetryMax/infraRetryBaseMs/recoveryReport to the work config; spread cfg.work so the literal stays complete
[bot] 🏷 冲突已解决,CI 全绿 master 在评审期间合入了 #6(workdir GC)、#8(pending-replay infra retry)、#11(restart recovery),与 #10 在 10 个文件上冲突。已将 冲突处理方式(全部为两侧语义的并集,无一方被丢弃):
验证:
PR 当前 mergeable/clean。合并动作留给你。 中文摘要:已把 master(含 #6/#8/#11)并入分支解决全部 10 处冲突,均为两侧语义并集(web 测试库 tmpdir+uuid、daemon 测试配置展开继承新键、config 键集合并),本地与 CI 三件套全绿且相对 master 零回归,PR 可合并。 |
|
继续 |
[bot] 🏷 Agent 侧工作已全部完成,当前状态如下 刚核实过(
剩下唯一的一步是合并 PR——这一步我不能做:系统规则禁止 agent 合并任何 PR(即使有人指示),合并只能由人来执行。请你自己合并:#10 如果你说的"继续"是指我此前报告的那几个观察项(web 测试服务器端口窗口重叠、共享测试库的 中文摘要:PR #10 全部开发与验证已完成且 CI 全绿、可干净合并;合并操作受系统规则限制只能由人工执行;后续观察项需另开 issue 才会进入开发。 |
Closes #7
Changes (the four spec items)
runtime/env-deny.tsextendsENV_DENY_ALWAYSwithDAEMON_DB_PATH,WORK_DB_PATH,OPENCODE_DB_PATH,DAEMON_ENV, shared by the opencode and pi backends. Spawned agent processes (and any test run they launch) can no longer open the engine DB through inherited environment. Test spawns a fake binary that dumps its own env and asserts the keys are absent.tests/setup.tsscrubs the four vars plusWORK_DB_PREFIXand pinsWORK_DB_PATHto a unique file underos.tmpdir()(pid + uuid; pid alone collides under bun pid recycling across parallel files). A root-levelbunfig.tomlwires the same preload so barebun testfrom the repo root cannot reach production or default DB paths. A child-process regression sim proves an inheritedDAEMON_DB_PATHcannot redirect the test DB.ensureOwnedre-creates a vanished issue row before claiming (a recreated row gets a fresh local uid, so downstream references use the returned row) and, on foreign-key failure, re-registers the daemon identity viahealDaemonRowthen retries the claim once. NewStore.releaseDanglingOwners()clears owners pointing at deleted daemons rows - a casereleaseDeadOwnersstructurally cannot see - wired at boot and in the observer cycle.work.reconcileScopes/WORK_RECONCILE_SCOPES) and backfills missing rows as active without spawning work. Automates the manual backfill done during the bc#853 incident.Verification
tsc --noEmit: cleanFindings (reported in #7, no code changes here)
bun testmixes both packages in one process env (nested bunfig files are ignored) - pre-existing landmine, out of scope here