Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs-site/src/content/docs/reference/cli/lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ Run opencodex as a login-managed background service (macOS **launchd**, Linux **
Windows **Task Scheduler**) that auto-starts on login and auto-restarts on crash. Service runs set
`OCX_SERVICE=1` so a restart does not churn the Codex config.

The Windows wrapper verifies its baked Bun runtime and CLI entry before every start attempt. If an
interrupted package update removed either file, it logs one `installation is incomplete` message and
stops instead of retrying the same missing executable every five seconds. Reinstall opencodex, then
run `ocx service repair` to refresh the task with the restored package paths.

| Subcommand | Action |
| --- | --- |
| none | Create/update and start the service. |
Expand Down
8 changes: 8 additions & 0 deletions src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1545,6 +1545,14 @@ export function buildWindowsServiceScript(entry = cliEntry(), port = resolveServ
'>>"%OCX_SERVICE_LOG%" echo opencodex_home="%OPENCODEX_HOME%"',
'>>"%OCX_SERVICE_LOG%" echo codex_home="%CODEX_HOME%"',
'>>"%OCX_SERVICE_LOG%" echo token_file="%OCX_API_TOKEN_FILE%"',
'if not exist "%OCX_BUN%" (',
' >>"%OCX_SERVICE_LOG%" echo [%DATE% %TIME%] installation is incomplete: bundled Bun is missing; reinstall opencodex, then run ocx service repair',
" exit /b 3",
")",
'if not exist "%OCX_CLI%" (',
' >>"%OCX_SERVICE_LOG%" echo [%DATE% %TIME%] installation is incomplete: CLI entry is missing; reinstall opencodex, then run ocx service repair',
" exit /b 3",
")",
`"%OCX_BUN%" "%OCX_CLI%" start --port ${port} >>"%OCX_SERVICE_LOG%" 2>&1`,
"if %ERRORLEVEL% NEQ 0 (",
' >>"%OCX_SERVICE_LOG%" echo [%DATE% %TIME%] child exited with code %ERRORLEVEL%; restarting in 5s',
Expand Down
10 changes: 10 additions & 0 deletions structure/06_docs-and-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ bun install --frozen-lockfile
bun run build
```

## Windows service wrapper and incomplete updates

[Decision Log]
- 목적과 의도: Prevent a failed npm replacement from making the Task Scheduler wrapper retry missing package files forever.
- 기존 구현 및 제약 조건: The wrapper deliberately restarts a proxy after runtime crashes, but an absent baked Bun or CLI path cannot recover inside that process. Current updater preflight and stop-first behavior reduce replacement risk but do not provide a transactional restore of npm's package tree and global launchers.
- 검토한 주요 대안: Keep unconditional five-second retries, add a generic crash ceiling, restore npm directories in-place, or classify only proven missing executable paths as terminal.
- 선택한 방식: Check the baked Bun and CLI paths before every spawn; log one actionable incomplete-install message and exit with code 3 when either is absent. Preserve the existing retry loop for a child that actually launched and then failed.
- 다른 대안 대신 이 방식을 선택한 이유: A generic retry ceiling can stop a service after unrelated intermittent crashes, while copying a package directory without matching npm shims, ownership, and lock guarantees is not a safe rollback.
- 장점, 단점 및 영향: File-less package skeletons no longer produce unbounded service logs or restart churn. The wrapper still recovers ordinary proxy crashes, but repairing an incomplete npm install remains an explicit reinstall plus `ocx service repair` operation until a verified staged-update design exists.

## GitHub workflow map

| Workflow | Trigger | Purpose |
Expand Down
24 changes: 24 additions & 0 deletions tests/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,30 @@ describe("Windows service task", () => {
expect(script).not.toContain("timeout /t");
});

test("stops instead of restart-looping when an update removed the baked runtime or CLI (#1849)", () => {
const script = buildWindowsServiceScript({
bun: "C:\\OpenCodex\\bun.exe",
bunRuntimeSource: "bundled",
cli: "C:\\OpenCodex\\cli.ts",
});
const loopAt = script.indexOf(":loop");
const bunCheckAt = script.indexOf('if not exist "%OCX_BUN%"');
const cliCheckAt = script.indexOf('if not exist "%OCX_CLI%"');
const launchAt = script.indexOf('"%OCX_BUN%" "%OCX_CLI%" start --port');
const retryAt = script.indexOf("goto loop");

expect(loopAt).toBeGreaterThanOrEqual(0);
expect(bunCheckAt).toBeGreaterThan(loopAt);
expect(cliCheckAt).toBeGreaterThan(bunCheckAt);
expect(launchAt).toBeGreaterThan(cliCheckAt);
expect(retryAt).toBeGreaterThan(launchAt);
expect(script).toContain("installation is incomplete: bundled Bun is missing");
expect(script).toContain("installation is incomplete: CLI entry is missing");
expect(script.match(/exit \/b 3/g)).toHaveLength(2);
// `goto loop` re-enters both checks before another child spawn.
expect(script.slice(loopAt, launchAt).match(/if not exist/g)).toHaveLength(2);
});

test("rewrites profile-relative paths to env indirection so non-ASCII usernames survive OEM-codepage batch parsing", () => {
const oldUserProfile = process.env.USERPROFILE;
const oldAppData = process.env.APPDATA;
Expand Down
Loading