Conversation
2026-08-05 CI incident: the effect-mode run froze for 13 minutes with zero output after "worktree.create: shared use done" until the 15m step timeout. The scenario timeout and the bounded() cleanup guards are all timer-based — they cannot fire when a native-level hang (instance dispose / tree-sitter / sqlite teardown) freezes the event loop itself, which is the class of failure the 2026-07-27 hardening did not cover. The only guard that survives a frozen event loop is a separate process: - the runner heartbeats a file on every scenario/phase transition (--progress mode, i.e. CI) - a child process polls it every 5s; after 120s of silence it prints the last recorded scenario/phase and SIGKILLs the runner - a silent 15-minute freeze becomes a 2-minute attributed failure; the step timeout stays as the final backstop Verified: worktree scenario subset passes with the watchdog armed; a synthetic event-loop freeze is killed in ~4s (3s timeout + poll) with the last-activity diagnostic, exit 137.
fix(test): out-of-process watchdog for the httpapi exerciser
- 超时信号:节点超时不杀子会话;持久化 NodeTimeoutEscalated 与 timeout_extensions,wake 主 agent 裁决(report_to_parent=false 的升级节点同样送达,F11) - 延长路径:replan 携带新 worker_config.timeout_ms → 重算绝对 deadline;§3.7 变更门控 + A1 cap gate(deadline 已过期或 escalation_pending 才放行,防循环改值绕过上限);F2 省略 timeout_ms 不隐式缩短既有延长 - 上限兜底:timeout_extensions 达上限 → 强制 cancel + nodeFailed(timeout);计数 per-attempt 累计(NodeStarted/Restarted 清零),配合 maxNodeReplanAttempts 构成全生命周期上界(§6:COUNT-based,默认 10min 下单次尝试 ≈3.5h) - 失败安全:N1 extend 写入前置(写失败/dead/block 保留旧 watcher,监督不缺席);D1 per-node catchCause(单点 extend 失败不中断 sweep/spawnReady/checkCompletion);written 行数可观测(guard 拒绝 = 0 行) - 崩溃恢复:orphaned pending/adoption 恢复(Effect.ensuring 释放槽位);deadline watcher 自续轮询(S1/F5/F8) - escalation_pending 列 + 两个独立 migration(按 id 至多执行一次);summary/TUI/httpapi 透出 escalatedNodes(F10 指示) - 测试:test/dag 359 pass(含 N1/D1/A1/F1B 失败路径与 cap 回归) - chore: oxlint warning ratchet 4734 → 4831;其中 4826 为本批 DAG 提交态实测(新增测试沿用既有 as-never 惯例的 no-unsafe-type-assertion),另 5 为工作树内同批未入本 PR 的非 DAG 改动贡献
feat(dag): node timeout escalation — signal, adjudication, cap backstop
…single transaction - publishMany:一批 durable 事件单事务提交,聚合内 seq 连续、投影按序、提交后单次 wake(BatchEvent 接口 + 聚合一致性校验拍平为 early-return,无 else) - notify fan-out 契约:listener 在 layer scope 绑定 fiber 上 fork 执行——单事件内按注册序、失败/慢 listener 不阻塞 publish;跨事件顺序不保证(契约注释 + event.test.ts 断言对齐为顺序无关) - commitDurableEventInner 提取为事务作用域单事件提交(seq 分配/owner 校验/投影/UPSERT+INSERT),publish 与 publishMany 共用;wakeDurable 提取 - session fork 收敛为单事务(publish 各自开 savepoint 子事务),大 session fork 一次 commit - 测试:event-batch(批提交/seq 连续/聚合校验/投影序/wake 一次)+ fork-batch(单事务收敛、@ts-ignore 镜像 sqlite driver 既有模式并有债务注释) - chore: oxlint ratchet 4831 → 4842(新测试沿用 as-never 测试惯例的 no-unsafe-type-assertion 计数;全树实测)
feat(core): batch durable publish + listener fan-out contract + fork single transaction
- HttpOptions 新增 timeout(DurationFromMillis,Schema.optional);多份 options 合并时取最后一个显式 timeout(findLast,与 entries lowest→highest 优先级约定一致) - http transport 将 timeout 应用到请求(超时语义 = 无数据产出即超时,Stream.timeoutOrElse per-pull) - 测试:transport-timeout.test.ts 覆盖超时触发/未触发/合并优先级
feat(llm): per-request timeout through http transport options
…ry, snapshot dedupe - agent 配置新增 timeout 字段(NonNegativeInt;运行时 0/未设回退默认 600s,杜绝 0=立即超时);resolveTurnTimeout 按 Config.entries lowest→highest 取最后匹配(findLast,修全局配置压过项目配置的优先级反转) - DEFAULT_PROVIDER_TURN_TIMEOUT 独立常量(packages/core 不跨包引用 opencode dag 配置,注释说明) - history 增量读取(afterSeq 游标)+ decode 回归 Schema.decodeUnknownEffect typed 错误通道(去 try/catch 与 as 断言) - runner 光标缓存随 drain 全出口 ensuring 逐出,baselineSeq 校验防陈旧;批处理 withBatch 收敛 - snapshot 助手更名 captureDeduped(名实相符:始终 capture、tree ID 去重),调用点同步 - 测试:history-incremental、session-runner-hotpath(busy-wait 改有界 waitUntil+timeoutOrElse,stub 按 test AGENTS.md 约定)、tool-events 适配
feat(core): session runner hot path — turn timeout, incremental history, snapshot dedupe
- fetchRemoteJson 双路降级:初始 fetch 失败与 response body 读取/解码失败均返回 undefined(源不可达),由 fetchRemoteJson 记 warning
- wellknown 源不可达 → 跳过该源(continue),本地配置完全可用;secondary remote_config 不可达 → 合并空({}),与主路径行为一致
- 测试:wellknown-offline.test.ts 覆盖 fetch 失败与 body 读取中途报错两条降级路径
fix(config): degrade gracefully when remote config source is unreachable
- 无 assistant 回复 → 可见暂停(pauseAndPublish + ⏸ 提示),替代静默 stall - 僵尸目标新鲜度守卫:active + 零续跑 + 超阈值 + 无 assistant → 可见可恢复暂停(probe limit:1,stale 路径早退不拉全量窗口) - 纯工具调用轮(无文本输出)→ 合成 continue verdict 直接续跑(受 turn budget 约束) - 中断分支:保留 no-pause 语义并补 F1 论证(status.ts 无条件发 Status+Idle,run-state cancel/idle 均 set idle;中断后下一 idle 事件必然到达)——证据审查推翻了「静默 active」担忧;使用 fiber 安全 pauseAndPublish(非 goal.pause,防自中断悬置) - e2e-loop.test.ts +333 行:4 分支全覆盖(无-assistant 暂停 / 合成 continue / 状态变更暂停 / 中断分支),沿用既有 fixture 模式
feat(goal): stall-resistant pause/resume with full branch coverage
…het reconcile, test-race stabilization 来源:dev→main 晋级前深度 review(Round 1: dag_025b31d42ffeB5451We6bFWE4Y,4 路对抗审查 + 声明核验 + 仲裁;Round 2: dag_02568e2a8ffe5YB5f1WdiYvALw,R-3 修正波;终审 PASS,5/5 标准闭合)。 - goal/loop.ts: Cause.interruptors → Cause.hasInterrupts(R-8/F1,HIGH):interruptors 只收集有定义的 fiber id,漏匿名中断(Cause.interrupt()),会把用户 ESC 误判为 dispatch 失败触发错误暂停;hasInterrupts 为结构化判定 - package.json: oxlint ratchet 4842 → 4852(CI 实测值,本地 2894 文件 vs CI 2911 文件口径差已归档) - dag-loop-recovery-integration.test.ts: 补 pollWithTimeout 同步点(R-3 唯一 NEEDS-FIX 位点,镜像 dag-orphan-pending-recovery 既有模式);R-3 穷举审计 22 套 36 位点,其余全 CLEAN - dag-timeout-escalation-fixes.test.ts: 消除 8s sleep 对 5s retry 的竞态(F2,12/0 过) - share-next / workspace / cli-process: 三个既有 timing flake 的稳定化(production debounce 与测试预算对齐、eventuallyEffect 放宽、子进程并发串行化)——CI 定性 ENVIRONMENT,main 基线同态复证 - e2e-loop.test.ts: hasInterrupts 修复后的 interrupt 变体覆盖 - httpapi-exercise/watchdog.ts: 心跳 tmpdir 泄漏清理(F6)
fix(core): deep-review findings — hasInterrupts interrupt check, ratchet reconcile, test-race stabilization
…ead window start/extend/replan accepted graphs whose inline prompt_template referenced variables with no binding source (not in prompt_template.input, input_mapping, or depends_on identity); every such node died at spawn with verdict_fail, leaving a silent window where the wave was reported running but already dead. - dag.ts: templateBindingErrors validator beside conditionReferenceErrors, wired into create and replan/extend acceptance (same rerun-node filter); rejects the whole call naming node and unbound variables - templates/resolve.ts: export placeholderKeys as single source of truth for template syntax - id templates are read lazily from disk and stay spawn-time enforced (documented asymmetry); wake-integration coverage kept via a real template fixture - workflow.md: orchestration discipline 'acceptance is not execution' documents the residual orchestrator-side rules
… tickets and orchestration manifest
… deadline event + command guard, S5 lock timeout, flaky stabilization Integrates 8 tickets (Q1-Q3, S5, SDK verify, share-next InstanceRef fix, share-next test stabilization, workspace history-replay fix): - Q1: escalation_pending now has a contract — cleared on every terminal transition (NodeCompleted/Failed/Skipped/Cancelled), orthogonal to the wake_reported delivery flag. - Q2: re-time gate (loop.ts) gains a delivery-gated SKIP conjunct (escalationPending && !wakeReported); adjudication must follow delivery. - Q3: nodeExtendTimeout abolished the store.updateNodeDeadline direct write; the guard (status='running' + Q2 gate) moved to the command layer BEFORE publish, so NodeDeadlineExtended is the success log and the projector is a pure idempotent fold. store.updateNodeDeadline removed. - S5: withWorkflowLock capped at WORKFLOW_LOCK_TIMEOUT (30s) via Effect's builtin TimeoutException — one line, zero new error class, 14 callers intact. - Stabilization: workspace syncHistory forwards replayed events with workspace id (mirrors live-SSE); share-next test drops Effect.sleep fiber-ready antipattern and the masking 15s budget. - share-next subscriber restores per-instance InstanceRef context on the forked listener fiber; getModel gated on an existing share. Verification: typecheck clean (opencode+core); test/dag 376/0; test/cli/run 194+5skip/0 (x2); test/share 7/0 (x2); test/control-plane 37/0 (x2); lint reconciled 4852->4888 (+36 idiom-consistent no-unsafe-type-assertion warnings from two new dag test files using the established 'as never' idiom).
…ect = -2 keeps watcher) Audit-confirmed batch-A regression: the command-layer guard's 0 return became 2-valued (terminal-reject vs Q2 delivery-gate reject while still running), but the sole caller treated every 0 as terminal and killed the watcher — a still-running node lost supervision (N1 violation), reachable under the T8/T9 interleave where evalLock and workflowLock are unsynchronized. Mechanism (a), minimum semantic change: Q2-reject now returns -2; the handler's existing written<0 branch keeps the watcher, 0 stays exclusively terminal. 3 new GAP-C1 handler tests assert watcher survival on Q2-reject and correct teardown on terminal-reject.
…ratchet The 6 new acceptance-binding tests inherited the file's legacy 'as Effect.Effect<never>' cast. They were compile-time redundant (typecheck exit 0 without them), and on CI each fires no-unsafe-type-assertion, pushing the branch to 4858 > 4852 ratchet. Removing the 6 new casts (the 5 pre-existing baseline casts are untouched) brings the branch back to the dev local baseline; CI should reconcile to 4852. No expect() assertion semantics changed.
fix(dag): acceptance-time template binding validation removes spawn-dead window
feat(dag): batch A — escalation flag lifecycle, delivery-gated re-time, deadline event + command guard, lock timeout, flaky stabilization
…spurious T8 budget unit)
docs: record batch-A backlog findings (phantom cancelled state, spurious T8 budget)
| seen.wellKnown = request.url | ||
| return Effect.succeed(json(request, { remote_config: { url: "https://config.example.com/opencode.json" } })) | ||
| } | ||
| if (request.url.includes("config.example.com")) { |
…ring sanitization' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…ring sanitization' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
晋级内容(dev → main)
批次 A:DAG 引擎状态机语义(两轮深审 PASS 冻结设计 → 8 票实现 → C1 修正闭环)
#185:接受期模板绑定校验
start/extend/replan 接受期干跑占位符解析,无绑定来源当场拒绝——spawn 期静默死亡窗口结构性消灭
dev 全量门禁证据(4ddeaf2fc)
后续(不阻塞本晋级)