opencodex Bun 运行时覆盖与版本诊断指南用OPENCODEX_BUN_PATH定位 Windows 服务运行时问题【免费下载链接】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 项目中一项面向 Windows 服务/运行时排障的专项改造——Bun 运行时覆盖override与版本诊断展开。其核心目标是在服务异常难以定位时让用户能够精确获知 opencodex 实际将使用哪一个 Bun 运行时路径与来源并允许通过OPENCODEX_BUN_PATH环境变量人为指定一个可信的 Bun 二进制用于紧急复现或缓解问题。读完本文你将掌握OPENCODEX_BUN_PATH的取值规则与验证门槛、运行时选择的完整回退顺序、ocx status中的运行时诊断信息如何解读以及 Windows 服务包装器如何固化运行时路径并附上完整的测试与验证命令。本文对应的原始计划文档为 devlog/_fin/260628_windows-gptpro-followup/40_cycle4_bun_runtime_diagnostics_plan.md属于「260628 Windows GPT Pro follow-up」工作切片的 Cycle 4参见 00_followup_slice_map.md计划中的能力在当前源码树中均已落地实现。一、背景为什么需要运行时覆盖与诊断opencodex 是一个通用 Provider 代理运行时依赖 Bun。在 Windows 上代理通常以计划任务Task Scheduler等后台服务形式常驻而服务由脚本包装器拉起用户无法像在前台终端那样直观地看到「到底用的是哪个 Bun」。在 GPT Pro 后续审查见 devlog/_fin/80_windows-codex-path-hardening/16_gpt_pro_followup_review_3fe1286.md中反馈集中在两点运行时路径诊断有帮助但用户仍缺乏一个受支持的OPENCODEX_BUN_PATH覆盖通道无法在不手工编辑生成的服务文件的前提下用 canary/修复版 Bun 进行测试需要一套受校验的覆盖机制由 CLI 启动器与服务安装共享且必须能区分「覆盖来源」「内置来源」「当前进程来源」三种运行时出处。Cycle 4 的计划由此展开其目标一句话概括原文 GoalMake Windows service/runtime investigations easier by exposing the exact Bun runtime opencodex will use, and by allowing a deliberate Bun binary override for emergency reproduction or mitigation.二、核心机制一OPENCODEX_BUN_PATH覆盖读取器与大小门槛校验2.1 环境变量与覆盖读取计划要求为src/bun-runtime.ts现位于src/lib/bun-runtime.ts新增OPENCODEX_BUN_PATH覆盖读取器。当前实现中环境变量名定义于 src/lib/bun-runtime.tsconst BUN_OVERRIDE_ENV OPENCODEX_BUN_PATH;纯 Node 启动器bin/ocx.mjs在 Bun 加载任何 TypeScript 之前运行因此同样以常量形式复刻了该名称bin/ocx.mjsconst BUN_OVERRIDE_ENV OPENCODEX_BUN_PATH; // Mirrors BUN_RUNTIME_SOURCE_ENV in src/lib/bun-runtime.ts. This launcher is plain // Node and runs before any TypeScript is loaded, so the name is repeated rather than // imported; tests/cli/ocx-launcher-source.test.ts pins the two together. const BUN_RUNTIME_SOURCE_ENV OCX_BUN_RUNTIME_SOURCE; const BUN_RUNTIME_PATH_ENV OCX_BUN_RUNTIME_PATH;tests/cli/ocx-launcher-source.test.ts会将这两个文件中的同名常量绑定在一起校验防止未来同步漂移tests/cli/ocx-launcher-source.test.ts 断言BUN_OVERRIDE_ENV的字面量。2.2 大小门槛size gate拒绝占位符 stubopencodex 通过bunnpm 依赖分发 Bun 运行时esbuild 风格一个小主包 平台相关的oven/bun-*optionalDependencies由包自身 postinstall 的node install.js完成下载。在 postinstall 未执行时bin/bun.exe位置会留下一个约 450 字节的 ASCII 占位脚本运行即报错。因此覆盖值只有在指向「真实的 Bun 二进制」时才被接受。校验函数isRealBunBinary()位于 src/lib/bun-binary-validator.mjs门槛常量与契约如下// The bun package leaves a tiny ASCII placeholder at bin/bun.exe until its // postinstall downloads the real ~60MB binary. Keep the threshold and the // false-on-filesystem-error contract shared by the Node launcher and Bun code. export const REAL_BUN_MIN_BYTES 1_000_000; export function isRealBunBinary(path) { try { return existsSync(path) statSync(path).size REAL_BUN_MIN_BYTES; } catch { return false; } }规则要点阈值文件存在且大小 ≥ 1,000,000 字节1MB才视为真实二进制契约任何文件系统错误不存在、不可读一律返回false不抛异常共享该门槛由纯 Node 启动器与 Bun 侧代码共用保证两边判定一致。测试tests/ci-workflows/bun-runtime.test.ts中「isRealBunBinary (size gate vs placeholder stub)」分组覆盖了四种情形tests/ci-workflows/bun-runtime.test.ts场景构造方式判定结果占位 stub写入一段报错 shell 脚本约 450 字节false真实二进制分配 1,000,000 字节true路径不存在指向不存在的.exefalse空文件写入 0 字节false2.3 启动器中的覆盖读取与回退bin/ocx.mjs的resolveBun()展示了完整的选择逻辑bin/ocx.mjsfunction resolveBun({ allowInstall true } {}) { // Keep direct package-launcher starts aligned with durable service/shim installs: // a valid explicit runtime must win even when the bundled dependency exists. const override process.env[BUN_OVERRIDE_ENV]?.trim(); if (override) { const overridePath resolve(override); if (isRealBunBinary(overridePath)) return { path: overridePath, source: override }; console.error( opencodex: ${BUN_OVERRIDE_ENV} is missing, unreadable, or not a complete Bun binary; falling back to the bundled runtime., ); } // ... bundled dependency resolution ... }注意两个细节值先trim()空白包括 Windows 上常见的引号残留/首尾空格不会误判为覆盖路径经resolve()转为绝对路径后再校验相对路径会基于当前工作目录解析相对覆盖在 launcher 场景下是允许的因为 launcher 在 Bun 加载项目 dotenv 之前就已选定二进制。三、核心机制二运行时选择回退顺序与持久化解析3.1 回退顺序计划明确规定回退顺序为valid override → bundled Bun →process.execPath这一点在 src/lib/bun-runtime.ts 的durableBunRuntime()/unmarkedDurableBunRuntime()中得到印证function unmarkedDurableBunRuntime(): DurableBunRuntime { const bundled bundledBunPath(); if (bundled) return { path: bundled, source: bundled, overrideEnv: BUN_OVERRIDE_ENV }; return { path: process.execPath, source: process, overrideEnv: BUN_OVERRIDE_ENV }; }优先级来源source值说明1合法覆盖已通过大小门槛override由 launcher 或持久化标记携带2内置 Bunbun依赖的bin/bun.exe或bin/bunbundled位于包管理器全局目录ocx update后依然存活3当前进程可执行文件process当bun依赖缺失时如源码 dev checkout 直接bun src/cli/index.ts运行bundledBunPath()的实现要点src/lib/bun-runtime.ts通过require.resolve(bun/package.json)定位依赖目录依次探测bin/bun.exe与bin/bun后者为前向兼容预留并同样经过isRealBunBinary()校验。3.2 持久化解析与「来源/路径」成对标记服务、shim 等持久化产物不能每次启动都重新解析环境——因为服务进程在 Bun 加载项目 dotenv 之后若再重读OPENCODEX_BUN_PATH项目.env就能把任意可执行文件写进 shim 或服务定义安全隐患。因此durableBunRuntime()采用启动时打标、运行时读回的策略启动器选定二进制后写入一对环境标记OCX_BUN_RUNTIME_SOURCE来源与OCX_BUN_RUNTIME_PATH二进制路径见bunRuntimeProvenanceEnv()src/lib/bun-runtime.tsreportedBunRuntimeSource()读回标记时要求来源与路径成对且路径指向当前process.execPath否则一律视为「未知」绝不猜测src/lib/bun-runtime.ts——这正是 issue #848 的教训服务安装早于标记机制时没有出处记录臆测会给出「自信的错误答案」。路径比较通过samePath()进行规范化src/lib/bun-runtime.ts优先realpathSync解析真实路径以兼容符号链接/目录联接/映射盘符Windows 上再统一小写以容忍大小写差异解析失败时退回字面比较。DurableBunRuntime的类型定义集中了这三个字段src/lib/bun-runtime.tsexport type DurableBunRuntime { path: string; source: BunRuntimeSource; overrideEnv: typeof BUN_OVERRIDE_ENV; };BunRuntimeSource白名单仅为override | bundled | processsrc/lib/bun-runtime.ts线上来源或环境中的任何越界值都被当作不存在处理而不是透传。3.3execPath重启路径的来源延续ocx ensure、GUI 启动、重启、更新后重启都会以process.execPath重新执行当前运行时。withProcessRuntimeProvenance()src/lib/bun-runtime.ts负责为这些路径补齐/延续标记无继承标记时记录该可执行文件「真实来源」内置则为bundled否则process有继承标记时仅当标记记录的路径与即将执行的二进制一致才原样延续——标记会沿进程树传播可能比其二进制活得更久若换用了不同 Bun 还沿用旧标记就会用矛盾的出处重启守护进程测试 tests/ci-workflows/bun-runtime.test.ts 把 5 个会spawn(process.execPath)的启动点钉死确保任何新增 launcher 都不会悄悄丢失来源标记src/cli/index.ts、src/cli/claude.ts、src/cli/opencode.ts、src/server/management/system-restart.ts、src/update/index.ts。四、核心机制三ocx status运行时诊断4.1 数据采集collectStatus计划要求 CLI 在ocx status的 Runtime 诊断中包含 Bun 路径/来源同时保持既有 status 行为与退出码不变。当前实现中collectStatus()通过durableBunRuntime()一次性取得路径与来源src/cli/status.tsconst bunRuntime durableBunRuntime();随后写入 JSON 输出的两个位置src/cli/status.tspaths: { config: getConfigPath(), pid: getPidPath(), runtime: bunRuntime.path, }, runtime: { source: bunRuntime.source, ...(bunRuntime.source override ? { overrideEnv: bunRuntime.overrideEnv } : {}), },注意overrideEnv字段仅在来源为override时出现——这是向消费者明确「该路径来自哪个环境变量」的唯一出处字段。4.2 人读输出Runtime与Runtime sourceocx status的打印逻辑位于 src/cli/index.tsDashboard: http://localhost:10100/ Config: config 路径 PID file: pid 路径 Runtime: Bun 绝对路径 Runtime source: override (OPENCODEX_BUN_PATH) # 或 bundled / process来源为override时会追加显示触发它的环境变量名OPENCODEX_BUN_PATH来源为bundled/process时不显示括号后缀该输出行为由tests/ci-workflows/bun-runtime.test.ts与 status 相关测试固化保证诊断信息增加不影响命令退出码。4.3 doctor 的联动来源感知的引导门控#848运行时来源还服务于ocx doctor的「记忆/运行时」诊断src/cli/doctor.ts来源为override时输出「OPENCODEX_BUN_PATHis already active for this service — the override runtime is itself an affected version (unvalidated — own risk).」不再重复建议用户去设置OPENCODEX_BUN_PATH来源为bundled/process时才给出「wait for a bundled runtime update, or setOPENCODEX_BUN_PATHto a runtime you trust」。这正是 issue #848 的修复形态当覆盖已生效时doctor 绝不能再次建议用户去设置同一个变量。对应回归测试见 tests/codex-integration/doctor.test.ts。五、核心机制四Windows 服务包装器固化运行时5.1 一次性解析并固化计划要求 Windows 包装器记录选定的 Bun 路径并在存在覆盖时记录覆盖来源。实现遵循「路径与来源来自同一次解析」原则——cliEntry()src/service/state.ts将runtime.path与runtime.source作为一对返回注释明确指出不能二次调用durableBunRuntime()否则可能解析出与已固化二进制不同的结果export function cliEntry(runtime: DurableBunRuntime durableBunRuntime()): { bun: string; bunRuntimeSource: BunRuntimeSource; cli: string } { return { bun: runtime.path, bunRuntimeSource: runtime.source, cli: join(serviceSourceDir, cli, index.ts) }; }5.2 批次包装器写入标记Windows 服务脚本由 src/service/windows-taskxml.ts 的buildWindowsServiceScript()生成。包装器echo offchcp 65001 一系列set中固化了两枚运行时标记src/service/windows-taskxml.tsset OCX_BUN_RUNTIME_SOURCEsource set OCX_BUN_RUNTIME_PATHbun 路径其中source可能为override、bundled或process路径经windowsEnvIndirectBatchValue间接取值避免直接内联敏感路径并经过windowsBatchValue转义%→%%、^→^^、去引号、去换行。OCX_BUN_RUNTIME_SOURCE/OCX_BUN_RUNTIME_PATH与OCX_SERVICE1一起构成服务启动时读取的「我到底在用哪个 Bun」的持久化答案。launchd plist 与 systemd unit 走同一套durableBunRuntime()逻辑见 src/service/launchd.ts 与 src/service/systemd.ts跨平台行为一致。5.3 服务场景的使用时序重要针对服务安装文档明确强调见 docs-site/src/content/docs/troubleshooting/windows-memory.md对服务安装而言这个覆盖值是在生成服务产物时读取的而不是在服务启动时读取的。先设置环境变量然后在同一个 shell 中重新运行ocx service repair这样路径才会被写入持久化的服务定义。只设置环境变量对已经安装好的服务没有任何作用。也就是说# Windows PowerShell 示例 $env:OPENCODEX_BUN_PATH C:\path\to\bun.exe ocx service repair # 同一 shell 内重新固化服务产物服务测试 tests/service/service.test.ts 验证了「覆盖值由 launcher 消费后写入批次包装器环境」的完整链路OPENCODEX_BUN_PATH...出现在产物文本中并验证来源为bundled时产物中不包含覆盖变量tests/service/service.test.ts。六、范围边界与非目标计划明确了本次改造刻意不做的事避免范围蔓延原文 Non-goals不修改安装脚本或包管理器依赖版本——覆盖通道只是运行时选择入口不改变bun依赖本身的发布与安装方式测试中不执行任意 Bun 覆盖路径——校验只做静态判定存在性 大小门槛绝不 spawn 用户指定的二进制不改变 Codex shim 语义——shim 仍读取同一份 durable Bun 来源本次仅增加诊断可见性不改注入行为。此外00_followup_slice_map.md 记录了一项已知技术债src/server.ts、src/service.ts、src/cli.ts与部分测试超过 500 行准则本 cycle 保持小步修改完整拆分为独立重构。七、测试矩阵与验证命令计划要求扩展三类测试当前仓库对应实现如下计划中的测试文件当前仓库实际文件覆盖内容tests/bun-runtime.test.tstests/ci-workflows/bun-runtime.test.ts大小门槛stub/真实/不存在/空文件bundled/durable 解析override 不二次重选来源标记读回、白名单、成对校验execPath 重启来源延续5 个启动点被钉死tests/cli-help.test.tsstatus 打印相关测试含 tests/ci-workflows/ci-workflows.test.ts 等Runtime:/Runtime source:输出与 JSONruntime.source/overrideEnv期望tests/service.test.tstests/service/service.test.tsWindows 服务包装器固化来源标记override 生效/不生效两种产物形态补充的启动器级测试 tests/cli/ocx-launcher-runtime.test.ts 覆盖了「合法 override 被实际用于代理进程」「非法 override 回退并打印OPENCODEX_BUN_PATH is missing, unreadable, or not a complete Bun binary」两条路径。计划给出的验证命令可直接在仓库根目录执行bun test tests/ci-workflows/bun-runtime.test.ts # 计划中的 tests/bun-runtime.test.ts 已迁移至此 bun test tests/service/service.test.ts bun x tsc --noEmit八、从计划到现状文件位置的变化计划撰写时引用的文件路径src/bun-runtime.ts、src/cli.ts、src/service.ts与当前仓库实际位置略有出入——这些文件在后续「src 结构重组」工作中被拆分/迁移参见 devlog/_fin/260707_src-restructure/000_plan.md计划中的路径当前路径src/bun-runtime.tssrc/lib/bun-runtime.ts校验器拆分至 src/lib/bun-binary-validator.mjssrc/cli.tssrc/cli/index.ts诊断采集拆分至 src/cli/status.tssrc/service.tssrc/service/state.ts src/service/windows-taskxml.ts 等tests/bun-runtime.test.tstests/ci-workflows/bun-runtime.test.ts当前 structure/runtime.md 对src/lib/bun-runtime.ts的角色做了权威定义内置 Bun 解析isRealBunBinary()大小门槛、bundledBunPath()、durableBunPath()且「durable 选择只接受已为运行中可执行文件盖章的来源/路径对绝不重读项目 dotenv 中的OPENCODEX_BUN_PATH」——这正是本计划核心安全语义的最终表述。结语OPENCODEX_BUN_PATH覆盖 运行时来源诊断构成了 opencodex 在 Windows 上排查 Bun 相关服务问题的完整闭环覆盖通道让你在紧急时刻主动切换可信运行时诊断输出让你在任何时刻都能确认「实际生效的运行时是什么、从哪里来」。配合成对来源标记、1MB 大小门槛、持久化产物一次性固化与 doctor 引导门控这套机制在提供灵活性的同时牢牢守住了「绝不把未经校验的可执行文件悄悄写进服务定义」的底线。【免费下载链接】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创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
