opencodex 三轮审计驱动修复:Cursor 上下文连续性、错误分类与传输加固的 AUDIT-LOOP 实践

发布时间:2026/9/25 9:53:07
opencodex 三轮审计驱动修复:Cursor 上下文连续性、错误分类与传输加固的 AUDIT-LOOP 实践 【免费下载链接】opencodexUniversal provider proxy for OpenAI Codex Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code项目地址https://gitcode.com/gh_mirrors/ope/opencodex点击查看免费下载本文以 审计第三轮合成报告 为核心骨架完整还原 opencodex 项目中一次真实的多轮代码审计闭环针对 Cursor 适配器的三个线上缺陷会话上下文连续性丢失、resource_exhausted过度映射为 400、实时传输层两个稳定性缺口由同一审查者连续三轮审查FAIL → FAIL → GO-WITH-FIXES最终以 2 个 Medium 阻塞项的具名修正收尾。读完本文你将掌握审计循环AUDIT-LOOP的判定与折叠修正机制、连续上下文传播的store:false强制保留方案、gRPC 错误码的 400/429 拆分语义以及 HTTP/2 实时传输的单一终态守卫 兜底销毁 有界重试加固范式并能在当前仓库源码中逐行验证每一项落地实现。一、背景三个根因与一次审计闭环的起因2026-07-23opencodex 从线上运行数据~/.opencodex下的usage.jsonl、service.log、responses-state.json中定位到 Cursor 适配器的三个真实缺陷全部记录在 000_plan.md 中根因现象线上证据影响面RC1 — 会话连续性在store:false下失效totalTokens呈现 51676 → 107 → 78 → 111177 → 3455 … 的交错跳变而非单调递增的绝对上下文responses-state.json中0条含 cursor 续接状态Codex 每个请求都拿到全新的conversationId携带前向缓存永远命中不了最终done.usage.totalTokens只剩输出增量RC2 —resource_exhausted过度映射为 400连续 6 条 400 记录~2s 间隔upstreamErrorCursor resource limit exceeded: Cursor Connect error resource limit exceeded: ErrorCodex 盲目重试重试预算 5 → 6 条真正的配额/速率耗尽被当成客户端可修复错误客户端不退避反而猛打重试RC3 — 传输层缺口一条 504Cursor request timed out: Cursor transport timed out before first responseservice.log中Cursor model discovery ... failed [timeout]: No response within 8000ms与 HTTP/2 会话失败日志会话级 error 无监听器、超时清理只有close()没有destroy()兜底、模型发现对瞬态失败无有界重试修复被拆成依赖有序的三个阶段PHASE-SPLIT-01010_phase1_conversation_continuity.mdRC1、020_phase2_resource_exhausted_classification.mdRC2、030_phase3_transport_hardening.mdRC3。而每个阶段的文档产出都要过同一审查者的多轮审查这就是本次合成报告的核心语境。二、审计循环r1 FAIL(8) → r2 FAIL(6) → r3 GO-WITH-FIXES(2)003_audit_r3_synthesis.md 记录的判定演进本身就是工程方法论的浓缩Round history: r1 FAIL (8) → r2 FAIL (6) → r3 GO-WITH-FIXES (2, both folded) Main-agent judgment: near-pass — all High blockers resolved by r3; the two Medium residuals are folded as concrete doc amendments above. Exit A→B per AUDIT-LOOP-01.三轮审查逐轮收敛第一轮 8 个阻塞项High 5 / Med 2 / Low 1集中在测试打不到真实路径如Cursor 永远走runTurn分支通用站点 1824/1859 是死代码谓词测试绕过了真实策略属于同义反复、无终态守卫、字节上限缺失第二轮 6 个阻塞项进一步要求字节记账必须扛住加载/重置/替换、配额提示词必须先行拒绝、真实 h2 集成测试不能数私有回调。到第三轮全部 High 阻塞项已解决只剩 2 个 Medium 项被折叠为具体文档修正详见下文第三节判定达到 GO-WITH-FIXES 并按 AUDIT-LOOP-01 退出 A→B。两个有价值的审查纪律值得单独强调分别出自 001_audit_r1_synthesis.md 与 002_audit_r2_synthesis.md激活落地原则C-ACTIVATION-GROUNDING-01任何修改/测试都必须能通过真实可达的调用链激活杜绝为永远不会到达的分支写死代码。测试必须走真实策略不得绕道例如强制保留谓词必须导出后做单元测试 服务器级真实路由链测试而不是用一个自定义假实现自己测自己。三、第三轮的两个 Medium 阻塞项修正方案的完整细节r3 仅剩的两个阻塞项都与修正本身是否可验证/是否真正生效有关也是本文最值得展开的实战内容。3.1 阻塞项 1Cursor 分支绕过了模型发现失败冷却窗口审查发现Med, ACCEPTmarkModelsFetchFailure只记录一个时间戳src/codex/model-cache.ts当前实现见下文而唯一的读取方isModelsFetchCoolingDown位于 Cursor 分支之下原文定位catalog.ts:1562——Cursor 分支早已提前 return冷却判断对它永远不生效。后果是模型发现连续失败时每一次/v1/models轮询都要重新支付一次完整的发现超时最坏约 11.5 秒而不是冷却期内直接走 stale/静态目录降级。修正方案两处对称修改GUARD读侧在 fresh-cache 检查之后、fetchCursorUsableModels之前加入if (isModelsFetchCoolingDown(name))判断命中即返回与失败后一致的 stale/configured 降级结果MARK写侧发现失败路径上调用markModelsFetchFailure(name)。激活场景连续两次目录刷新且发现 stub 失败——第二次不得触发发现stub 调用次数保持 1。当前仓库中的落地证据该逻辑在后续目录重构后位于 provider-models.ts。其 Cursor 分支顺序为fresh-cache 命中getFreshCached→ 冷却检查isModelsFetchCoolingDown(name, undefined, undefined, authorityIdentity)命中则返回getStaleCached或 configured 的degraded结果→fetchCursorUsableModels实时发现 → 失败时markModelsFetchFailuremarkProviderDiscoveryFailed并回落到degraded。而 model-cache.ts 的isModelsFetchCoolingDown实现了审查中强调的authorityIdentity 按凭证隔离一个账号的 401/404 不能决定另一个账号没有目录未带身份记录的失败则保持全局抑制。3.2 阻塞项 2假 CursorTransport 绕过实时传输跟踪器rekey 无法验证审查发现Med, ACCEPT第三轮要补的外部模型 tool-result 轮换场景续接时主动铸造新conversationId并重排 usage 缓存在服务器级 E2E 里无法通过假 transport 验证——因为 rekey 调用发生在 live-transport 模块私有的跟踪器里假 transport 根本观察不到。修正方案最小的诚实接缝服务器 E2E只断言轮换 持久化链式请求中 transport 收到的是新的conversationId且持久化的续接状态存的就是这个新 IDrekey 的调用验证下沉到适配器层createCursorAdapter的 deps 增加可选rekeyContextUsage参数默认绑定真实的rekeyCursorContextUsage适配器单元测试注入 spy断言轮换路径上它以(oldId, newId)被调用。这条修正还隐含了对 010 文档的约束同 ID 连续只适用于 NATIVE / 非轮换请求外部模型 tool-result 的轮换是刻意保留的行为src/adapters/cursor/request-builder.ts铸造新 ID连续性在那里意味着新铸造的 ID 被持久化给下一轮。从当前源码看rekeyCursorContextUsage已作为导出函数存在live-transport.ts由cursorContextUsageTracker.rekey支撑。3.3 minor 项测试观测点的措辞修正审查还顺带修正了一处观测口径集成冒烟观察的是LiveCursorTransport.run()async iterable以恰好一次抛出的传输错误终止而createCursorAdapter.runTurn()发射的是error 事件而非抛出——测试应锚定传输层而非适配器层避免断言错位。四、第三轮修正背后的传输加固全貌Phase 3两个 Medium 阻塞项实际折叠进了 030_phase3_transport_hardening.md 的三项有界修改中。结合当前 live-transport.ts 源码其落地形态如下。4.1 单一终态守卫createTerminalSettleropen()原本没有settled 守卫failAndClear直接调fail、流end独立调finish一旦补上会话级 error 监听器就会出现end 迟到 session error、timeout session error双重终态变更。审查 r2 blocker 4 进一步指出私有回调无法从公开 API 触达必须提取并导出才能单测。当前源码已按此落地live-transport.ts语义与文档一致fail/finish 谁先到谁拥有终态后到者只做clearTimer安全空操作export function createTerminalSettler(hooks: { fail: (error: Error) void; finish: () void; clearTimer: () void; }): { settleFail: (error: Error) void; settleFinish: () void; settled: () boolean } { let settled false; return { settleFail(error) { if (settled) return; settled true; hooks.clearTimer(); hooks.fail(error); }, settleFinish() { if (settled) return; settled true; hooks.clearTimer(); hooks.finish(); }, settled: () settled, }; }4.2 会话级 error 监听与幂等终结实时 transport 原先只在session.on(connect)上注册TLS/socket/GOAWAY 会话错误可能绕过失败路径Node 语义下ClientHttp2Session未处理的error甚至可能直接抛异常。修正为在failAndClear声明后注册this.session.on(error, ...)统一走settleFail。幂等性由 4.1 的守卫保证会话错误在流 end 之后到达只会记日志不会触发第二次终态回调。4.3 首帧超时的强制幂等清理armTimeoutDestroyFallbackclose()会等待在途帧而死 socket 可能无视它。修正方案close()之后立即武装一个可注入宽限期的销毁兜底定时器默认CURSOR_TIMEOUT_DESTROY_GRACE_MS 1_000测试可注入timeoutDestroyGraceMs缩短定时器 unref 不阻塞进程退出。当前源码live-transport.tsexport function armTimeoutDestroyFallback( stream: { destroyed: boolean; destroy: () void }, session: { destroyed: boolean; destroy: () void }, graceMs: number, ): ReturnTypetypeof setTimeout { const timer setTimeout(() { try { if (!stream.destroyed) stream.destroy(); } catch { /* gone */ } try { if (!session.destroyed) session.destroy(); } catch { /* gone */ } }, graceMs); timer.unref?.(); return timer; }从当前源码调用点看超时路径已统一settleFail首帧超时、Connect 帧错误、意外 EOF 等均走 live-transport.ts 附近的守卫destroy 兜底随之幂等。4.4 有界发现重试先拆分transport/http再重试审查 r1 blocker 6 的洞察已完成的非 2xx 响应此前也被归为http若凡 http 皆重试会重试确定性的 404/503。修正分两步拆分错误类别CursorUsableModelsResult的失败分支增加transport会话错误、请求设置失败、流错误、连接设置失败均在响应前与http已完成的非 2xx确定性、不重试并列只重试瞬态RETRYABLE_DISCOVERY_ERRORS new Set([timeout, transport])每次重试都用全新HTTP/2 会话每次调用自带http2.connect不引入连接池第二次尝试超时上限DISCOVERY_RETRY_TIMEOUT_MS 3_000中间加 250~500ms 随机抖动。当前 live-models.ts 已完整落地结果联合类型含transport | http等分支fetchCursorUsableModels外壳 内部fetchCursorUsableModelsOnce当前函数体重命名实现单次有界重试。延迟预算文档显式接受的权衡缓存未命中时最坏 ≈ 8000ms首次 ~500ms 抖动 3000ms封顶重试≈ 11.5s已有旧目录降级缓存吸收重复轮询。五、前两轮审查沉淀Phase 1 与 Phase 2 的关键决策r3 的通过建立在 r1/r2 的 14 个阻塞项全部收敛之上其中有三个决策值得随文保留。5.1 强制续接只给 kiro/cursoradapterNeedsForcedContinuationrememberResponseState()在请求带store:false时跳过持久化除非force:true而 Codex 对每个非 Azure HTTP 请求都发store:false。审查 r1 blocker 1 纠正了关键事实Cursor 永远走runTurn分支src/server/responses.ts:1483通用适配器站点1824/1859永远看不到 cursor在那里强制续接是死代码。修正只替换两个可达三元组:1537 / :1574并导出一个可单测谓词export function adapterNeedsForcedContinuation(name: string): boolean { return name kiro || name cursor; }同时为强制保留引入字节高水位MAX_STORED_RESPONSE_BYTES 64MBmeasuredEntry唯一计算尺寸、deleteEntry唯一删除、ensureLoaded重算尺寸、setResponseStateByteCapForTests测试接缝因为每次续接扩展都会把前序条目完整复制进下一请求链路字节数近似平方级增长——这个风险 kiro 早已存在字节上限一并修复。5.2resource_exhausted的 400/429 拆分配额提示词先行gRPCRESOURCE_EXHAUSTED通常是配额/速率耗尽只有显式工具目录/注册过大文本才应映射为 400tool_catalog_too_large。审查 r2 blocker 3 明确拒绝自由提示词配对配额/速率提示词必须最先拒绝然后才匹配短语级尺寸模式。当前 cursor-errors.ts 已实现isCursorRequestTooLargeDetail配额提示词[too many requests, quota, rate limit, rate-limit, throttl]先行拒绝classifyCursorError的分支too-large 细节 →Cursor resource limit exceeded400其余resource_exhausted含裸Error尾巴、quota、limit reached→Cursor rate limit exceeded429Codex 退避、combo failover 视为瞬态。判定表如下消息样本分类结果映射resource_exhausted: tool registration too largeresource limit exceeded400tool_catalog_too_largeresource_exhausted: request exceeds maximum allowed sizeresource limit exceeded400resource_exhausted: too many requestsrate limit exceeded429resource_exhausted while loading tool catalog: quota exhaustedrate limit exceeded429resource_exhausted: Error裸rate limit exceeded4295.3 测试接缝策略能导出就导出真实 h2 只做冒烟r2 blocker 4 的结论贯穿三个 PhasecreateTerminalSettler/armTimeoutDestroyFallback直接从源码导出并用 fakes 做确定性单测真实本地http2.createServer()只承担集成冒烟断言 public 表面不做回调计数发现重试用同一本地服务器验证首击 stall 超时 → 二击 200 → 服务器观察到 2 个请求401 场景则只有 1 个请求。六、验证命令与可复现的验收标准整套工作的验证入口来自三个 Phase 文档与 000_plan.md 的 Loop specbun run typecheck bun test tests/responses-state.test.ts tests/cursor-request-builder.test.ts \ tests/cursor-protobuf-events.test.ts tests/server-combo-failover-e2e.test.ts bun test tests/cursor-errors.test.ts tests/adapter-error-inline.test.ts \ tests/request-log.test.ts tests/errors-adapter-failure.test.ts bun test tests/cursor-hardening.test.ts tests/cursor-live-transport.test.ts bun run test # 全量按 C-ACTIVATION-GROUNDING-01 整理的验收要点store:false的 Cursor 请求完成后previousResponseProviderState(id)?.cursor?.conversationId等于适配器实际使用的会话 ID带previous_response_id的后续请求恢复同一 IDsettler 单测覆盖 fail-then-fail / fail-then-finish / finish-then-fail / finish-then-finish钩子恰好触发一次clearTimer恰好调用一次destroy 兜底在destroyedfalse时对 stream 与 session 都调用destroy()已销毁则跳过发现重试仅对timeout/transport生效auth401与已完成的 404http不重试冷却窗口内第二次目录刷新不触发发现stub 调用次数为 1字节上限最旧条目被逐出、最新链节存活重启重算后上限仍生效同 ID 重复记忆不重复计数。七、方法论小结什么让这个审计闭环真正收敛从 r1 的 8 个阻塞项到 r3 的 2 个 Medium 折叠修正收敛的本质是三条纪律事实锚点不断刷新r1 Low blocker 8所有文件/行号引用在每轮审查中重新核对例如发现真实调用者从discovery.ts修正为catalog.ts:1538测试接缝在文档期就定死而非构建期临场发挥r1 blocker 7可导出的纯函数、可注入的依赖参数、可替换的测试工厂全部先写进规格诚实的最小接缝r3 blocker 2服务器 E2E 只断言能被假 transport 观察到的行为轮换 持久化观察不到的 rekey 调用下沉到适配器层用 spy 覆盖——不为可测性扭曲测试观测点。这套 AUDIT-LOOP-01 模式与上述全部阶段文档、源码落地、测试命令均可直接在仓库中复核阶段计划见 000_plan.md三轮合成见 001、002、003实现代码可对照 live-transport.ts、live-models.ts、cursor-errors.ts、model-cache.ts 与 provider-models.ts 逐行验证。赞分享【免费下载链接】opencodexUniversal provider proxy for OpenAI Codex Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code项目地址https://gitcode.com/gh_mirrors/ope/opencodex点击查看免费下载相关推荐OpenCodex Cursor 适配器修复实录store:false 下的会话上下文延续、resource_exhausted 错误分级与 HTTP/2 传输加固OpenCodex Cursor 适配器修复实录store:false 下的会话上下文延续、resource_exhausted 错误分级与 HTTP/2 传opencodex PR 73 Cherry-Pick 与传输加固Codex/Cursor 桥接层的错误分类与取消语义修复opencodex PR 73 Cherry Pick 与传输加固Codex/Cursor 桥接层的错误分类与取消语义修复 导读 本文基于 opencodexOpenCodex Cursor 适配器上下文连续性修复对抗式审计 8 大 Blocker 的裁决与源码级修复全解OpenCodex Cursor 适配器上下文连续性修复对抗式审计 8 大 Blocker 的裁决与源码级修复全解 本篇基于 OpenCodex 仓库 dev创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考