Repository navigation
[Bug][Responses] After a short request makes fold inactive, resending canonically equivalent history fails to restore co | [Bug][Responses] 短请求使 fold inactive 后,规范化等价的历史重发无法恢复压缩覆盖 #4050
Description
(ai: OpenAI/GPT-6)
问题与范围
在 Responses legacy render 路径下,同一 session 的短请求会暂时令已有 fold inactive。之后主历史仅改变空白、使内容哈希 ID 变化时,reconcile 没有恢复原本可以恢复的覆盖:块仍保留,但摘要退出出站请求,已折叠原文重新发送。
这是当前 master 的真实 HTTP proxy + loopback mock upstream 复现,不只是提取函数的单元测试。只修改测试文件,没有提交实现修复。测试刻意关闭 snapshotRender(默认 0%),存储关闭;没有测试 D/snapshot lane,也不声称它存在同一缺陷。生产配置未配置 snapshot/rollout,保留日志没有该 session 的 snapshot-render 记录,但缺少历史进程环境,故不能仅凭这些断言现场 lane。
建议分类:bug / p2 / codex / scope/wiring;讨论类别 I(其他宿主车道)。
已验证的四组对照
固定提交 bc4a3f87e2dc98e82df6f707d362e75492fdf3d1(检查时仍是 master),bili 0.1.202,Windows 10 x64 / Node 24.15.0。
通过真实 HTTP 请求建立 24 条合成主历史,用实际 createCore().applyCompression 折叠前七条,再发送主历史建立覆盖。短请求为同 session 的 3 个 raw input items,每个 13 个 input_text parts。等价重发只把普通空格变为两个空格;规范化文本和顺序保持一致。
| 场景 | 短请求后 active | 重发后 active | 出站含摘要 | 出站含已折叠 assistant 原文 | 出站字节 |
|---|---|---|---|---|---|
| 无短请求 → 空白变化 | — | 1 | 是 | 否 | 65,884 |
| 短请求 → 完全原样重发 | 0 | 1 | 是 | 否 | 63,850 |
| 短请求 → 同样的空白变化 | 0 | 0 | 否 | 是 | 80,867 |
| 无短请求 → 真正不同的正文(负控制) | — | 0 | 否 | 否(正文已替换) | 22,409 |
统一基线出站为 63,850 bytes;受影响场景比无短请求的等价重发多 14,983 bytes。以上是合成请求的字节测量,不是 token 计费测量。最早 user message 按现有保护策略保留,断言检查的是已实际折叠的第二条 assistant 消息,不将受保护首条误计为展开。
四个测试全部通过(断言明确记录当前缺陷;修复后应改变受影响场景的预期),并有真编辑负控制。没有外部模型调用,mock upstream 使用已有测试 helper 的响应形状,没有模拟 Codex 客户端的原生压缩。
代码链条
- Responses prepare 先 reconcile,再
core.processTurn。 - syncBlocks 在短视图不存在覆盖 ID/摘要 carrier 时将块置为 inactive,块本身保留。
- coveredRealHistoryIds 跳过 inactive 块。
- reconcileFoldCoverage 因
covered.size === 0提前返回。即使新主历史只是规范化等价的 ID churn,也没有 re-key;后续 sync 无法靠旧 ID 恢复。完全相同的 ID 则可以恢复,正好由对照证明。
这里报告状态组合和可验证行为,不建议对所有 inactive 块盲目恢复,也不要求把 prepare 无条件搬到 compaction gate 前。真正编辑/删除必须继续诚实失配;kernel 变更仍需遵循 owner 边界。
与已有问题/PR的区别
- [Bug][Responses] Short wire requests expand past the fold-reconcile guard and reap main-session blocks #3844 / 已合并 [PR] fix: preserve main folds across expanded short Responses requests #3845:保护 raw short request 不向 reconcile 提供删除/清理证据。此复现就在含该修复的 master 上,只有一次短请求,块没有被 reap;遗留的是 inactive + ID churn 的恢复缺口。
- [Suspected][Codex] After the short view deactivates blocks, the immediately following compaction trigger may be erroneou | [Suspected][Codex] 短视图失活 blocks 后,紧接着的 compaction trigger 可能被 gate 误放行 #4036 / 尚未合并 [PR] fix: judge codex compaction gate against the request's own view #4040:压缩 trigger 到来时对本次视图做纯 activity probe;本报告没有 compaction trigger,故障发生在下一次普通完整 Responses 请求。[PR] fix: judge codex compaction gate against the request's own view #4040 不修改这里的 reconcile 路径,且保留 90% usage gate。
- After an interleaved metadata refresh, the entire history of the same session is resent in a different form, and already-collapsed content fails to re-anchor. | interleaved 元数据刷新后,同一会话整段历史换形态重发,已折叠内容 re-anchor 认不回来 #3301:较广的 form/身份恢复问题,现场包含迁移记录缺少比较材料。本复现的状态由当前真实管线建立,材料足够(无短请求时同样 ID churn 能恢复)。
- [Bug] Model-switch (session-generation change) leaves a stale fold ledger resident: compress reports
already compressed (e.g. b1..b43)next to0 active block(s), and the breaker never offers acp_status #4038:DSH model-switch 后already compressed/ breaker 的诊断与恢复问题;此复现不换模型,不调用失败的 compress,不依赖该错误分类。 - [resilience] Preflight compress/timeout loop on sustained mass fold-coverage loss has no graceful degrade #2438:大规模覆盖丢失后的 preflight 韧性;这里是更早的可复现覆盖恢复入口。
- [Bug][Codex] trigger/prompt forge only hands off fold summaries, omitting uncompressed recent work content | [Bug][Codex] trigger/prompt forge 仅交接 fold summaries,遗漏未压缩的近期工作内容 #4035:本地 forge 的未折叠 tail 交接遗漏,发生于不同路径;本复现没有 forge。
现场关联:观测已确认,因果仍有边界
脱敏的 Codex Desktop 多图会话,bili 0.1.202 build 6deb454 / rollout cli 0.163.0-alpha.10.2。主请求实际 input 230,122,之后短请求 raw 3 items 经处理为 66 CoreMessages、input 22,662;再回到主请求时出站从此前 35.5 MiB 升到 102.0 MiB,实际 input 591,601。compaction trigger 被 90% usage gate 放行,随后本地 rollout 确认原生 compaction。
这不能证明现场每个丢覆盖的块都由上述状态组合造成。 没有保留关键时刻逐块 active/ID 快照;现场还涉及模型切换和大量历史图片。token/请求体膨胀、短请求、原生压缩均有日志证据;将整个膨胀归因于这个 bug 仍是待验证假设。复现本身是纯文本,未声称复现 59 万 tokens 或图片膨胀。
下面是原始日志的精选字段;只替换 session UUID,不包含请求正文、图片、凭据、本机路径或项目名。时间为 UTC。
2026-10-20T18:44:46.200Z [info] [sess=session-A] [v=0.1.202] [session-A] [acp-usage] round 1 input=230122 cached=227200 (cache hit 99%)
2026-10-20T18:44:47.194Z [info] [sess=session-A] [v=0.1.202] [session-A] processTurn: 66 msgs, renderTags=text-only, 65 text tagged, 0 tool tagged (should be 0 with text-only)
2026-10-20T18:44:47.195Z [info] [sess=session-A] [v=0.1.202] [session-A] nudge idle: usage=119% (230122/193800), growth=28115/22500 (ref=202007, interval=50000), pendingT1=0/50000 model=gpt-5.6-luna, reason="EMERGENCY: usage 119% but max pending 1301 < min benefit 5000 tokens (T1 effective 0, T2 0, T3 1301) — suppressed: rewri"
2026-10-20T18:44:47.440Z [info] [sess=session-A] [v=0.1.202] [session-A] request: 3 msgs, inbound=1.9MiB, outbound=1.9MiB, local=867ms, view=ws-expanded
2026-10-20T18:44:55.292Z [info] [sess=session-A] [v=0.1.202] [session-A] [acp-usage] round 1 input=22662 cached=4608 (cache hit 20%)
2026-10-20T18:45:15.618Z [warn] [sess=session-A] [v=0.1.202] [session-A] context 630981.6115112097 tokens reached preflight target 320625 (model window 356250, model=gpt-6.1-sol; usage-baseline=22662 local-est=237802); preflight compressing before forward
2026-10-20T18:45:16.104Z [info] [sess=session-A] [v=0.1.202] [session-A] preflight folded 0 range(s) but images alone (~393179.61151120975 tokens) keep the estimate over window 356250 with no upstream overflow evidence — forwarding for the upstream to arbitrate billing (#496/#1800)
2026-10-20T18:45:16.173Z [info] [sess=session-A] [v=0.1.202] [session-A] request: 782 msgs, inbound=75.8MiB, outbound=102.0MiB, local=17998ms
2026-10-20T18:47:10.202Z [info] [sess=session-A] [v=0.1.202] [session-A] [acp-usage] round 1 input=591601 cached=39424 (cache hit 7%)
2026-10-20T18:47:25.360Z [info] [sess=session-A] [v=0.1.202] [session-A] codex compaction_trigger request not intercepted (gate preconditions not met) — forwarding verbatim (no preflight, no rebuild, no window clamp)
可运行的 HTTP 最小复现
在上述 commit 的工作副本安装依赖并构建 kernel。把下方代码保存为 tests/rabbit-short-replay.test.ts。测试使用已有仓库 helper 的缩小副本,端口均由 OS 分配。
Windows PowerShell(独立测试副本;所有运行状态写入副本旁边的 test-state):
npm ci --ignore-scripts --no-audit --no-fund
npm --prefix kernel run build
$testState = Join-Path (Get-Location) '../test-state'
$env:XDG_CONFIG_HOME = Join-Path $testState 'config'
$env:XDG_DATA_HOME = Join-Path $testState 'data'
$env:XDG_STATE_HOME = Join-Path $testState 'state'
$env:XDG_CACHE_HOME = Join-Path $testState 'cache'
$env:BILI_ROLLOUT_SNAPSHOT_RENDER = '0'
node --import tsx --test tests/rabbit-short-replay.test.ts完整合成 HTTP 回归(四组对照)
import { writeFileSync } from "node:fs";
import assert from "node:assert/strict";
import http from "node:http";
import { once } from "node:events";
import test from "node:test";
process.env.NODE_ENV = "test";
process.env.BILI_ROLLOUT_SNAPSHOT_RENDER = "0";
// Fail fast on the very first 429 instead of the default 3 attempts with
// exponential backoff — these tests are about the post-retry behavior.
process.env.BILI_REPLAY_RETRY_MAX = "1";
import { createCore, defaultConfig, type Config } from "acp-kernel";
import { startServer } from "../src/server.ts";
import { type CompressSettings, type ProxyOptions } from "../src/config.ts";
import { SessionStore, _setStoreForTest } from "../src/persist.ts";
import { getSession } from "../src/session.ts";
import { _setForTest as setRegistryForTest } from "../src/registry.ts";
// #301: when preflight (overflow) compression cannot proceed — the summary
// upstream is rate-limiting, or the compress budget is exhausted — the proxy
// must FAIL FAST with a structured error instead of forwarding the over-window
// payload as-is (guaranteed upstream 400, wasted quota, client retry storms;
// log evidence in #292). Forwarding as-is stays legal ONLY when the payload's
// own estimate actually fits the window (the trigger floors on
// stats.lastInputTokens, which can be stale — #300 double-counted usage).
function okSse(inputTokens: number): string {
return (
`event: message_start\ndata: ${JSON.stringify({ type: "message_start", message: { id: "m1", role: "assistant", usage: { input_tokens: inputTokens } } })}\n\n` +
`event: content_block_start\ndata: ${JSON.stringify({ type: "content_block_start", index: 0, content_block: { type: "text", text: "" } })}\n\n` +
`event: content_block_delta\ndata: ${JSON.stringify({ type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "ok" } })}\n\n` +
`event: content_block_stop\ndata: ${JSON.stringify({ type: "content_block_stop", index: 0 })}\n\n` +
`event: message_delta\ndata: ${JSON.stringify({ type: "message_delta", delta: { stop_reason: "end_turn", stop_sequence: null }, usage: { output_tokens: 3 } })}\n\n` +
`event: message_stop\ndata: ${JSON.stringify({ type: "message_stop" })}\n\n`
);
}
type Call = { stream: boolean; body: string };
function makeUpstreamOk(calls?: Call[]): http.Server {
return http.createServer((req, res) => {
const chunks: Buffer[] = [];
req.on("data", (c: Buffer) => chunks.push(c));
req.on("end", () => {
const raw = Buffer.concat(chunks).toString("utf8");
let parsed: { stream?: boolean } = {};
try { parsed = JSON.parse(raw); } catch { /* keep {} */ }
calls?.push({ stream: !!parsed.stream, body: raw });
if (parsed.stream) {
res.writeHead(200, { "content-type": "text/event-stream" });
res.end(okSse(1000));
} else {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ content: [{ type: "text", text: "SUMMARY: the large recent user message was a deterministic load-growth payload; its raw content is no longer needed." }] }));
}
});
});
}
function startProxy(upstreamPort: number, models: Record<string, { context: number }>, kernelOverrides?: Partial<Config>, compressOverrides?: Partial<CompressSettings>): Promise<http.Server> {
_setStoreForTest(new SessionStore({ enabled: false }));
setRegistryForTest({});
return startServer({
port: 0,
host: "127.0.0.1",
upstream: "http://127.0.0.1",
routes: { [`http://127.0.0.1:${upstreamPort}`]: { models } },
modelContextLimit: 400_000,
kernelConfig: defaultConfig(400_000, kernelOverrides),
compress: { injectTool: true, injectNudge: true, ...compressOverrides },
promptCache: { routing: "auto" },
sessionHeader: "x-acp-session",
log: false,
debug: false,
passthrough: false,
passthroughSource: null,
autoUpdate: false,
autoRestartOnUpdate: false,
updateTag: "latest",
advisoryCheck: false,
releaseNotesCheck: false,
compat: { roles: {} },
streamErrorShape: "protocol",
mitm: { enabled: false, domains: [] },
} as ProxyOptions);
}
for (const scenario of ["isolated-normalized", "short-exact", "short-normalized", "isolated-edited"] as const) {
test(`rabbit-http ${scenario}`, async () => {
const calls: Call[] = [];
const upstream = makeUpstreamOk(calls);
upstream.listen(0, "127.0.0.1");
await once(upstream, "listening");
const upstreamPort = (upstream.address() as { port: number }).port;
const proxy = await startProxy(upstreamPort, { "gpt-short-wire": { context: 400_000 } });
await once(proxy, "listening");
const proxyPort = (proxy.address() as { port: number }).port;
const sid = `rabbit-http-${scenario}`;
const post = async (input: unknown[]) => {
const response = await fetch(`http://127.0.0.1:${proxyPort}/bili/http://127.0.0.1:${upstreamPort}/v1/responses`, {
method: "POST", headers: { "content-type": "application/json", "x-acp-session": sid },
body: JSON.stringify({ model: "gpt-short-wire", stream: true, max_output_tokens: 1024, input }),
});
const body = await response.text();
assert.equal(response.status, 200, body.slice(0, 300));
};
try {
const main = Array.from({ length: 24 }, (_, i) => ({
type: "message", role: i % 2 ? "assistant" : "user",
content: `main turn ${i} ${(`UNIQUE_${i}_stable_text `).repeat(110)}`,
}));
await post(main);
const session = getSession(sid);
const messages = session.lastMessages;
assert.ok(messages && messages.length >= 24);
session.state = createCore().applyCompression({
ranges: [{ startRef: "m00001", endRef: "m00007", summary: "Compressed main history: seven early turns preserved the investigation, implementation decisions, and verified test evidence." }],
messages, state: session.state, config: defaultConfig(400_000),
}).state;
await post(main);
assert.equal(session.state.blocks.length, 1);
assert.ok(session.state.blocks[0].active);
const baselineWireBytes = Buffer.byteLength(calls.at(-1)!.body);
const before = session.state.blocks[0].effectiveMessageIds.slice();
let activeAfterShort: number | null = null;
if (scenario.startsWith("short-")) {
const short = Array.from({ length: 3 }, (_, i) => ({
type: "message", role: "user",
content: Array.from({ length: 13 }, (_, j) => ({ type: "input_text", text: `utility ${i} part ${j}` })),
}));
await post(short);
activeAfterShort = session.state.blocks.filter(b => b.active).length;
assert.equal(session.state.blocks.length, 1, "raw-count guard retains the block");
assert.equal(activeAfterShort, 0, "short request deactivates retained block");
}
const replay = main.map(m => ({...m, content: scenario === "short-exact" ? m.content :
scenario === "isolated-edited" ? "A genuinely different replacement with no prior semantic meaning." : m.content.replaceAll(" ", " ")}));
await post(replay);
const wire = calls.at(-1)!.body;
writeFileSync(`../rabbit-wire-${scenario}.json`, wire);
const result = {
scenario, activeAfterShort, retainedBlocks: session.state.blocks.length,
finalActiveBlocks: session.state.blocks.filter(b => b.active).length,
coveredIdsChanged: JSON.stringify(before) !== JSON.stringify(session.state.blocks[0]?.effectiveMessageIds),
summaryOnWire: wire.includes("Compressed main history: seven early turns"),
foldedAssistantOriginalOnWire: wire.includes("UNIQUE_1_stable_text"),
baselineWireBytes, replayWireBytes: Buffer.byteLength(wire),
};
console.log("RABBIT_RESULT " + JSON.stringify(result));
assert.equal(result.summaryOnWire, scenario === "isolated-normalized" || scenario === "short-exact");
assert.equal(result.foldedAssistantOriginalOnWire, scenario === "short-normalized");
} finally {
proxy.close(); proxy.closeIdleConnections?.();
upstream.close(); upstream.closeIdleConnections?.();
await Promise.allSettled([once(proxy, "close"), once(upstream, "close")]);
}
});
}实际运行结果
[
{
"scenario": "isolated-normalized",
"activeAfterShort": null,
"retainedBlocks": 1,
"finalActiveBlocks": 1,
"coveredIdsChanged": true,
"summaryOnWire": true,
"foldedAssistantOriginalOnWire": false,
"baselineWireBytes": 63850,
"replayWireBytes": 65884
},
{
"scenario": "short-exact",
"activeAfterShort": 0,
"retainedBlocks": 1,
"finalActiveBlocks": 1,
"coveredIdsChanged": false,
"summaryOnWire": true,
"foldedAssistantOriginalOnWire": false,
"baselineWireBytes": 63850,
"replayWireBytes": 63850
},
{
"scenario": "short-normalized",
"activeAfterShort": 0,
"retainedBlocks": 1,
"finalActiveBlocks": 0,
"coveredIdsChanged": false,
"summaryOnWire": false,
"foldedAssistantOriginalOnWire": true,
"baselineWireBytes": 63850,
"replayWireBytes": 80867
},
{
"scenario": "isolated-edited",
"activeAfterShort": null,
"retainedBlocks": 1,
"finalActiveBlocks": 0,
"coveredIdsChanged": false,
"summaryOnWire": false,
"foldedAssistantOriginalOnWire": false,
"baselineWireBytes": 63850,
"replayWireBytes": 22409
}
]tests 4
pass 4
fail 0
Activity
[-][Bug][Responses] 短请求使 fold inactive 后,规范化等价的历史重发无法恢复压缩覆盖[/-][+][Bug][Responses] After a short request makes fold inactive, resending canonically equivalent history fails to restore co | [Bug][Responses] 短请求使 fold inactive 后,规范化等价的历史重发无法恢复压缩覆盖[/+]ranxianglei commentedon Oct 21, 2026
(ai: vllm-qwen/qwen3.8-27b)
查重: 不重复。 与相关条目逐一比对:
already compressed (e.g. b1..b43)next to0 active block(s), and the breaker never offers acp_status #4038/[resilience] Preflight compress/timeout loop on sustained mass fold-coverage loss has no graceful degrade #2438/[Bug][Codex] trigger/prompt forge only hands off fold summaries, omitting uncompressed recent work content | [Bug][Codex] trigger/prompt forge 仅交接 fold summaries,遗漏未压缩的近期工作内容 #4035:DSH 模型切换断路器 / 大规模损失后 preflight / local forge tail handoff,机制不同,不适用。标签: bug / codex / scope/wiring / p1。 p1 理由(从报告的 p2 上调):按 owner 2026-10-19 修订,缓存泄露类(上下文失控增长)一律 p1——现场证据正是 p1 示例「缓存命中崩坏导致成本爆炸」:主轮 input 230,122 → 591,601 tokens,cache hit 99% → 7%,outbound 35.5 MiB → 102 MiB,最终把会话推入 native compaction。
Bug 审核三步:
syncBlocks(kernel/src/sync.ts:100-125)把 covered id 不在视图内的 block 置active=false但保留 block([Bug][Responses] Short wire requests expand past the fold-reconcile guard and reap main-session blocks #3844/[PR] fix: preserve main folds across expanded short Responses requests #3845 的设计行为)→ 随后全量历史规范化重发到达,reconcileFoldCoverage(src/fold-reconcile.ts:1504-1509)的输入覆盖集来自coveredRealHistoryIds(src/session.ts:1666-1675),它跳过 inactive block →covered.size === 0→ early return → order-centric claim ladder(公告:release v0.1.193 修复dsh 切模型导致上下文暴涨问题,修复fork问题,修复claude codex dsh等自动压缩导致会话破坏问题 . 另外修复几十个bug. 我本地场景有限,未必都测全,麻烦大家小场景先测试下是否有问题.任何问题及时反馈. #2821/fix(dsh): model switch drops all folds — fold-reconcile #2396 detect-only cannot re-anchor rewritten tool-call ids → doom-probe 502 session death #2915)根本没跑 → block 的 effectiveMessageIds 永远钉在旧 id 上 → 后续每轮syncBlocks都找不到旧 id → 覆盖永久丢失。你的四组对照自洽:exact 重发能恢复因为旧 id 仍有效;规范化重发只在 block 仍 active 时能恢复(对照组 1);真编辑诚实失配(对照组 4)。expanded(用户手动展开)与 consumed 死谱系继续排除,避免 分析缓存泄露问题 #2293 的 missing 膨胀复发;能佐证的格 re-key 后由后续 syncBlocks 自然重新激活,不能佐证的保持诚实失配,走既有 z-streak reap / epoch verdict 完成生命周期——不开新的盲目恢复通道。改动集中在 src/fold-reconcile.ts,kernel 不动。正在实施:先在你的四组对照 HTTP 测试上对当前 master 跑出红基线,再修复 + 翻转受影响断言 + 补单元级恢复/诚实失配钉,然后提 PR。
ranxianglei commentedon Oct 21, 2026
(ai: vllm-qwen/qwen3.8-27b)
修复已提交:#4085 (Closes #4050,待 CI)
结构化八问
reconcileFoldCoverage构建候选集时只收「活跃块」(coveredRealHistoryIds跳过 inactive)。短请求让 kernel 失活块之后,候选集为空 → 整个恢复流程提前返回 → 旧 id 永远无法重新对齐 → kernel 的syncBlocks也再找不到它们。根因是候选口径把「可恢复的谱系」排除在外,不是某个判据通道出错——你说的两个对照(无短可恢复、精确重发可恢复)恰好证明材料充分、只差这一步。syncBlocks在下一轮管道中自然重新激活;无佐证的 cell 保持诚实失配,走既有生命周期(z-streak reap / 截断抑制 / epoch 判决)。kernel 未动,无任何盲目恢复。coveredIdsOf保持 active-only 语义)、[cache-ledger] netSaved 面板在 fold 覆盖坍缩后继续累计虚增——ledger 与实际重发行为无交叉校验 #2202 侧车请求门(仍跳过短 pass)、[Bug][proxy] unframed host-side history deletion leaves zombie fold blocks rendering carriers forever — one carrier flaps in/out every turn (persistent prefix-cache churn) #2695 reap 生命周期、Snapshot as session reconstruction: identity claim order takes priority, message hash demoted to reference signal (root- | 快照即会话重构:身份认领顺序优先,消息哈希降级为参考信号(根治 #3588/#3640 一类误判) #3641 epoch 判决——均保持。本地定向套件 138/138 绿 + typecheck 干净;全量以 CI 为准。行为变更披露(有意变更,均在 PR 描述中有完整表格):
expanded: true(同一排除语义,分类更锐利)。测试证据:issue 中的复现已固化为端到端 HTTP 回归
tests/issue4050-short-replay.test.ts(真实代理 + loopback mock upstream)。修复前 short-normalized 场景断言的是 bug 行为(finalActive=0、摘要离线、原文上线、+17,017B);修复后该场景与无短对照逐字节一致(65,884B vs 65,884B),真正编辑的负向对照保持诚实失配(不盲恢复)。另有 4 个新单测钉住:churn 重铸双块、合法头截断永不 reap、中段编辑累积诚实漂移、consumed 子块 id 去重。判定:修复闭环,待 CI 绿后可合并。
style: separate top-level describes with blank lines (#4050 test block)
fix(tests): verif harness reds on solver-less CI runners — committed …
ranxianglei commentedon Oct 21, 2026
(ai: vllm-qwen/qwen3.8-27b)
CI 进展更新:PR #4085 全部静态门禁(kernel-guard / config-surface-guard / version-guard / docs-sync / lane-guard / lock-sync / lifecycle-ledger-guard …)+ 全部 e2e 车道(local-registry、codex-persona、native-opencode、fake-codex、native-dsh、pi-codex-ws、native-pi)通过。ubuntu-22 两个 test shard 红——排查结论:三处失败全部是 master 存量问题,与本 PR diff 无关(本 diff 不碰 verif/ 与 kernel/):
verif-wire-anthropic-roundtrip2 项 ENOENT/home/dog/tmp/bili-verif2/kernel/src/wire/anthropic.ts(google rebuild 同文件同病灶)→ 任何 runner 上都读不到源文件verif-view-latch-discipline1 项 ERR_TEST_FAILURE已对照 master 自身 CI 证实是存量:master 最近推送(56a0f5a,即本分支的 base)的 CI run 39528617351 同样红——ubuntu-22 shard2 同样的
/home/dogENOENT、shard3 同样的No module named 'cvc5'。顺手修掉了(独立标注 commit
354240b98,CI 红类小修按「当前 PR 顺手修」纪律处理,不另开 issue):new URL("../kernel/src/wire/*.ts", import.meta.url)——与其他 verif harness 既有模式一致;{ skip: !haveSolver }——照抄同族 google 文件的现成写法,未发明新机制。双向本地验证:有求解器环境 8/8 全跑全过;模拟无求解器环境(
CVC5_HOME/Z3_BIN指向不存在路径,即 ubuntu-22 runner 的实际状态)5 个求解器用例 SKIP、3 个真代码基线照过。已 push,等这轮 CI 出来再报最终状态。fix(tests): issue4050 regression test — cross-platform temp path for …
fix: recover fold coverage after short-request deactivation under id …
style: separate top-level describes with blank lines (#4050 test block)
fix(tests): issue4050 regression test — cross-platform temp path for …
Merge pull request #4085 from ranxianglei/2026-10-21_issue4050-short-…