opencodex Windows 后台服务:从 Task Scheduler 控制台窗口修复到原生 SCM(WinSW)服务的 fail-closed 生命周期设计
【免费下载链接】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 上提供两种后台常驻形态默认的 Task Scheduler 计划任务后端以及通过ocx service install --native接入的原生 SCMWindows 服务控制管理器后端。本文以 devlog 中 260720_windows_service 实施总结 为主线完整还原这一实施路线的四个工作包WP1–WP4与后续审计修复VBS 隐藏窗口启动器如何解决计划任务弹出控制台窗口issue #165、WinSW 原生服务如何以 SHA-256 固定与 LocalSystem 回滚验证保证安全、unknown状态如何把 fail-open 生命周期改造成 fail-closed以及更新与后端切换如何做到事务性与无冲突。读完你将对 opencodex 的 Windows 服务架构、命令行用法与安全边界获得源码级的理解。一、背景Windows 服务模式的两种后端opencodex 是面向 OpenAI Codex 与 Claude Code 的通用 provider 代理其后台服务在三个平台分别由 launchdmacOS、systemdLinux和 Windows 的进程管理器托管。在 Windows 上历史实现只依赖 Task Scheduler 计划任务一个名为opencodex-proxy的固定任务通过schtasks /create注册任务动作指向一个 cmd 批处理包装器再由该包装器循环拉起 Bun 运行时代理进程。这一形态由src/service/state.ts中的TASK opencodex-proxy与windowsServiceScriptPath()生成opencodex-service.cmd等路径定义src/service/windows-ops.ts的installWindows()/startWindows()/stopWindows()承担日常生命周期管理。计划任务形态有两个被社区反复报告的短板正是本次实施路线要解决的issue #165——控制台窗口计划任务的批处理动作会在交互会话中弹出可关闭的 cmd 窗口issue #166——非原生服务计划任务不是真正的 SCM 服务无法满足开机即以系统服务方式无窗口运行的运维预期。实施总结给出的四个工作包WP1–WP4在 040 号路线图内全部落地分别对应计划任务修复WP2、原生 SCM 服务WP3与更新路径保留WP4。二、WP2VBS wscript 启动器与隐藏窗口修复issue #165计划任务弹出控制台窗口的根因在于任务动作直接以可见窗口运行 cmd 包装器。修复方式不是改写批处理本身而是在中间插入一层 wscript 启动器任务动作改为调用一个 VBS 脚本由WScript.Shell.Run以窗口样式 0隐藏启动批处理包装器。这一实现位于 src/service/windows-taskxml.ts 的buildWindowsLauncherVbs() OpenCodex service launcher — runs the batch wrapper with a hidden window. Generated by ocx service install; do not edit. Set shell CreateObject(WScript.Shell) shell.Run script, 0, True这里的0即隐藏窗口样式True表示bWaitOnReturn——wscript.exe 保持驻留直到包装器退出。这一细节同时保证了计划任务的生命周期语义任务保持运行中状态、MultipleInstancesPolicyIgnoreNew防止重复实例、schtasks /end仍有可终止的任务实例。反之如果没有启动器控制台批处理动作就会在交互会话显示一个可关闭的 cmd 窗口即 issue #165。非 ASCII 路径的编码防御实施总结强调了一个容易被忽略的跨编码问题非 ASCII 配置文件路径例如韩文用户名目录在部分 WSH/代码页组合下会被 BOM-less UTF-8 VBS 误解码。为此生成的 VBS 与任务 XML 统一采用UTF-16LE BOM编码写入见src/service/windows-ops.ts的writeWindowsSchedulerAssets()// UTF-16LE BOM: a BOM-less UTF-8 VBS mis-decodes non-ASCII (e.g. Korean) profile // paths on some WSH/codepage combinations — same contract as the task XML below. writeServiceAssetWithRetry(windowsLauncherVbsPath(), \uFEFF${buildWindowsLauncherVbs(script)}, utf16le); writeServiceAssetWithRetry(windowsTaskXmlPath(), buildWindowsTaskXmlDocument(...), utf16le);批处理包装器自身则通过chcp 65001 nul切换到 UTF-8 代码页由于窗口已被隐藏这种切换不会泄漏到用户的交互 shellsrc/service/windows-taskxml.ts。此外启动器使用绝对 wscript 路径而非依赖 PATH 查找避免 OEM 代码页批处理解析带来的不确定性——WinSW XML 因为是 Unicode则不需要这种间接层。三、WP3opt-in 原生 SCM 服务WinSW 后端计划任务不是真正服务的缺口由--native选项补齐Windows 上可通过ocx service install --native注册一个真正的 SCM 服务托管方是WinSW v2.12.0Windows Service Wrapper。该后端完全可选opt-in默认仍是计划任务。服务标识与安装产物原生服务的 SCM 服务 ID 为opencodex-proxy-nativesrc/lib/winsw.ts与计划任务名opencodex-proxy刻意区分。安装产物落在配置目录下configDir/winsw/opencodex-proxy-native.exe—— WinSW 二进制首次安装时下载configDir/winsw/opencodex-proxy-native.xml—— 服务定义WinSW 按同基线名自动发现 XML日志滚转至configDir。SHA-256 固定下载fail-closedWinSW 二进制不打包进 npm 包首次--native安装时从官方发布下载。下载校验是严格 fail-closed 的src/lib/winsw.ts已存在本地 exe 时先核对 SHA-256不匹配则删除并重新下载下载后哈希与固定值WINSW_SHA256v2.12.0 官方 NET461 构建655872 字节不一致即抛错拒绝安装未验证的服务二进制网络失败时错误信息会给出手动放置路径提示把官方 WinSW.NET461.exe v2.12.0 放到指定位置后重试离线环境因此仍可安装。对应测试见 tests/service/winsw.test.ts伪造响应体[1,2,3]触发/SHA-256 verification/拒绝伪造离线错误触发手动放置路径提示。服务账户拒绝 LocalSystem服务定义通过 v2serviceaccount节绑定到当前登录用户domain/user/allowservicelogon绝不以 LocalSystem 运行src/lib/winsw.ts。原因在源码注释中写得很清楚opencodex 的令牌文件 ACL 加固只授予当前用户 SIDSYSTEM 服务读不到令牌文件反之 SYSTEM 拥有的写操作会改变用户访问契约。XML 中永不嵌入 API 令牌值——代理启动时从OCX_API_TOKEN_FILE指向的文件读取。服务环境还烘焙了OPENCODEX_HOME、CODEX_HOME、PATH等变量因为 SCM 服务不继承交互用户的 PATH源码注释引用 issue #764而 provider 子进程可能需要它。install /p 交互式凭据提示WinSW v2.12 的install /p会在控制台提示服务账户密码要求 stdin 继承。runWinswInteractive()因此显式检查process.stdin.isTTY非交互/隐藏会话直接报错要求从提权后的 Command Prompt 或 PowerShell 窗口运行src/lib/winsw.ts。LocalSystem 回滚验证安装完成后assertServiceAccountApplied()通过sc.exe qc读取SERVICE_START_NAME验证服务确实注册为当前用户若解析出 LocalSystemWinSW 在 XML 账户节被忽略/损坏时的默认值或与当前用户名不符则立即回滚卸载并抛错提示重跑ocx service install --nativesrc/lib/winsw.ts。重装已存在服务时跳过install /pWinSW 对已存在服务会报 service already exists改为重写资产并stopwaitstart重启避免重复输入凭据。支持的账户前提assertWindowsNativeServiceAccountSupported()额外把关当交互用户是 Microsoft 账户登录时SCM 无法为其认证直接拒绝--native提示改用计划任务后端或换成本地/域账户src/service/windows-ops.ts。四、fail-closed 生命周期WinswStatus四态模型实施总结中最重要的后续修正来自 sol 最终审计 blocker 1WinSWstatus查询失败曾被映射为nonexistent导致一个活着的 SCM 服务被排除在 stop/uninstall 目标之外——这是典型的 fail-open 缺陷服务还活着却被当作不存在生命周期操作直接跳过它。修复后WinswStatus为四态联合类型src/lib/winsw.tsstarted | stopped | nonexistent | unknownparseWinswStatus()只把 WinSW v2 的精确输出Started/Stopped/NonExistent映射到前三个状态任何不可解析的输出都归为unknown不当作不存在statusWinswRaw()在 exe 存在但查询抛错拒绝访问、exe 损坏/被隔离时返回unknown生命周期操作仍会尝试 stop/uninstallexe 缺失不证明SCM 注册已消失隔离、部分卸载会让陈旧注册存活Windows 上必须再用probeScmRegistration()向 SCM 本身确认查询失败返回errorfail-closed只有确认的ERROR_SERVICE_DOES_NOT_EXISTsc.exe 输出中的数字 1060语言无关才能证明不存在。probeScmRegistration()的 1060 判定值得注意sc.exe 不保证把 1060 行输出到 stderr 还是 stdout因此会扫描所有捕获流与错误消息Bun 可能把 1060 编码为低字节 status 36但 status 36 单独出现不被接受会与其他 mod 256 相同的 status 冲突必须要有文本 1060 佐证。这些分支都有精确的测试锁定tests/service/winsw.test.ts含葡萄牙语FALHA 1060、韩语1060: 지정된 서비스가...、stdout/stderr 两种通道。由此派生的 fail-closed 规则install状态查询返回unknown时显式拒绝安装绝不猜测安装状态src/lib/winsw.tsstopstopwait之后重新读 SCM 状态只接受stopped/nonexistent两种不可能再持有代理监听的形态unknown抛stop could not be verifiedsrc/lib/winsw.tsuninstallexe 缺失且 SCM 探测失败error时中止卸载并提示手工检查sc query opencodex-proxy-nativeexe 存在时uninstall失败会透传错误UAC 拒绝不能被静默吞掉仅 NonExistent 视为已卸载src/lib/winsw.ts。测试明确断言parseWinswStatus(garbage)必须等于unknown不可解析的输出不是不存在的证据install 在status: () unknown注入下必须拒绝tests/service/winsw.test.ts。五、事务性后端切换禁止双管理器共存原生服务与计划任务都试图复活代理若同时存在两个 manager 会在同一端口互相重生代理conflict。因此后端切换必须是事务性的切换向原生installWindowsNativesrc/service/windows-ops.ts先停止并卸载计划任务后端schtasks /delete验证其确实消失后才安装 WinSW 服务原生安装失败时明确报告现在没有任何服务绝不静默回退到计划任务。切换向计划任务removeNativeWindowsServiceForSchedulersrc/service/windows-ops.ts先卸载原生服务并轮询默认最多 20 次 × 250ms确认 SCM 注册达到nonexistent否则中止切换并提示手工sc delete opencodex-proxy-native。prepareServiceInstall()src/service/orchestration.ts是这条事务链的总入口它同时查询记录的后端与请求的后端冲突诊断diagnostic.conflict时两个后端都会被加入停止清单任何unknown状态或清理失败都会拒绝后续资产写入防止在旧监听器还活着时安装新资产。安装前的准备阶段还通过stopTrackedProxyIfRunning()停止可能单独占用的代理进程并做 PID 身份校验verifyPidIdentity与孤儿恢复findLiveProxy兜底保证端口真正空闲。停止语义同样区分classifyWindowsServiceStop把结果归为absent/stopped/stopped-respawnable/failed/state-unknown五种src/service/orchestration.ts。计划任务后端特有的stopped-respawnable是因为schtasks /end结束后cmd :loop包装器仍会存活几秒并重生子进程issue #764因此只有 Windows 计划任务需要支付 7 秒重启窗口轮询proxyStillLiveAfterStoplaunchd、systemd 和 WinSW 报告 stopped 即真的停止。六、WP4更新路径的后端保留与双后端查询ocx update期间必须保留用户选择的 backend不能因为升级把原生服务重装成计划任务。实施总结对应两个机制backend 保留重装serviceReinstallArgs()返回[service, repair]更新后的刷新动作走 repair 而非 install——repair自己发现已安装的后端健康的计划任务只刷新资产并重启陈旧的活定义才重新注册可能需要提权避免常规健康更新路径产生多余的 admin 提示src/service/state.ts。更新作业在src/update/job.ts中导入该参数src/update/job.ts并在 Windows 停止逻辑里按readServiceBackend()分流native走stopWinswService()否则走计划任务停止 包装器进程清杀src/update/job.ts。双后端总是查询停止与卸载路径始终同时查询两个后端而不是只信 state 文件stopServiceIfInstalledDetailed()同时探测计划任务与statusWinswRaw()任一后端活着都要停uninstallServiceDetailed()同理src/service/orchestration.ts。这是因为失败的切换或陈旧 state 可能让两个 manager 并存只查记录后端会让活的那个在ocx stop后重生代理。state v2 模式ServiceInstallState升级为 v2新增backend、winswVersion、winswSha256字段readServiceBackend()把 v1/遗留 state无 backend 字段统一映射为schedulersrc/service/state.ts。CLI 的parseServiceArgs()支持--native/--scheduler互斥旗标但明确限定--native只对install子命令有意义且仅限 win32 平台src/service/cli.ts。七、issue #168ocx update --help误触发真实更新实施总结还记录了一个 CLI 缺陷的修复ocx update --help原本会真正执行 self-update。修复在src/cli/index.ts与bin/ocx.mjs两个入口同时对 help 参数做短路处理实测验证两处都只输出 usage、退出码为 0 且无副作用。这类help 被当参数吞掉的问题在长参数解析器中很常见双入口都要修是因为ocx启动器与 bin 脚本走的是不同解析路径。八、证据与已知限制验证证据实施总结给出的验证矩阵focused 测试bun test tests/winsw.test.ts tests/update-stop-first.test.ts tests/service.test.ts tests/update-job.test.ts→69 pass / 0 fail对应仓库中的 tests/service/winsw.test.ts 等文件另有 tests/windows/winsw-stop-hardening.test.ts 对stopWinswService的验证语义做源码级断言bunx tsc --noEmitroot gui 全绿整体套件在本地并行运行时存在服务器/端口类间歇失败但同文件隔离运行全部通过被判定为本地负载型 flakepush 前有最终全量重跑记录。LOOP-PESSIMIST 遗留项Windows 实机 smoke 未执行服务安装/更新/窗口关闭的实测发生在 macOS 开发环境因此 #165/#166 评论区留有验证矩阵等待 Windows 用户回执后跟进updateChildStdio()的副作用非 TTY 环境下 stdio 统一切换为 pipe输出只在完成后批量 relay交互式进度显示会消失该副作用是 PR 原作者设计予以接受unknown状态下的 stop/uninstall对损坏 exe 可能额外抛错但结论是显式失败好过放任活服务无人管。九、命令行速查与适用前提# 计划任务后端默认Windows——隐藏窗口开机自启 ocx service install ocx service install --scheduler # 显式指定计划任务 ocx service status ocx service restart # 原生 SCM 服务Windowsopt-in需本地/域账户而非 Microsoft 账户 ocx service install --native # 首次安装下载并 SHA-256 校验 WinSW控制台提示服务账户密码 ocx service start / stop / status / uninstall # 更新与修复 ocx update # 更新后自动按已记录 backend 重装backend 保留 ocx service repair # 刷新已安装后端只在定义变化时重载适用前提--native为 Windows 专属选项非 win32 直接拒绝需要交互式控制台用于install /p密码提示首次安装需要网络下载 WinSW离线可手动放置官方 exe 后重试微软账户登录时原生后端不可用。若想深入源码建议从 src/lib/winsw.tsWinSW 封装与状态机、src/service/windows-ops.ts双后端安装/切换与 src/service/orchestration.ts事务性停止/卸载编排三处入手配套 tests/service/winsw.test.ts 的 fail-closed 用例阅读效果更佳。赞分享【免费下载链接】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 无窗口后台服务Task Scheduler S4U 与 WinSW 原生服务方案全解析opencodex Windows 无窗口后台服务Task Scheduler S4U 与 WinSW 原生服务方案全解析 导读 本文以 opencodexOpenCodex Windows 服务隐藏启动器改造wscript VBS 实现无控制台窗口的 Task Scheduler 后台服务OpenCodex Windows 服务隐藏启动器改造wscript VBS 实现无控制台窗口的 Task Scheduler 后台服务 本指南围绕 OpopenCodex Windows 原生服务 1060 误分类 RCA 拆解sc.exe 服务存在性探测的 fail-closed 生命周期判定openCodex Windows 原生服务 1060 误分类 RCA 拆解sc.exe 服务存在性探测的 fail closed 生命周期判定 本文基于仓库创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考