Repository navigation
bug(windows): tray icon exits after 3s and fails to launch detached host (ocx tray install) #2804
Description
Client or integration
Codex CLI
Area
CLI
Summary
When running ocx tray install on Windows:
- The command reports
Windows tray: installed and running. - Within ~3 seconds, the tray process silently terminates, and subsequent
ocx tray statuscalls reportWindows tray: installed, not currently running. - In the Windows taskbar, the icon remains as a "ghost" that instantly disappears upon right-click or hover.
Expected Behavior
ocx tray install should launch a persistent background tray process that remains running and displays the context menu on right-click.
Root Causes
-
Environment Dropped in Detached Host (
src/tray/windows.ts):
DETACHED_TRAY_HOST_LAUNCHERuses$startInfo.UseShellExecute = $true. In .NET,UseShellExecute = $trueinvokesShellExecuteEx, discarding process environment variables (OCX_TRAY_ENTRY_B64). Whenbun src/cli/index.ts __tray-hostlaunches without it,parseTrayHostEntry()throwsMissing tray host entryand exits immediately. -
Leftover Stop Signal Consumed on First Timer Tick (
src/tray/windows-tray.ps1):
signalTrayStop()sets the named eventLocal\OpenCodexTrayStop-<hash>. When the new tray starts in-Mode Run, it never resets this event. On the first 3000ms timer tick,if ($stopEvent.WaitOne(0))detects the leftover signal and calls[System.Windows.Forms.Application]::Exit(). -
WinForms Context Menu Styling & Non-Guarded I/O (
src/tray/windows-tray.ps1):
[System.Windows.Forms.Application]::EnableVisualStyles()is missing, causingContextMenuStriprendering issues. Additionally,$ErrorActionPreference = "Stop"causes minor I/O locks ontray-heartbeat.jsonto abort the UI thread.
Proposed Fix
1. Direct Launch via Native VBS Host in src/tray/windows.ts
Launch opencodex-tray.vbs via wscript.exe directly in spawnTray, matching the exact detached execution model used by Windows boot:
function spawnTray(state: WindowsTrayEntry): void {
const launcher = installedTrayLauncherPath();
if (existsSync(launcher)) {
const wscript = join(process.env.SystemRoot ?? "C:\\Windows", "System32", "wscript.exe");
execFileSync(wscript, ["//B", "//NoLogo", launcher], {
stdio: "ignore",
windowsHide: true,
timeout: 15_000,
});
return;
}
launchWindowsTrayHost(state);
}If retaining DETACHED_TRAY_HOST_LAUNCHER as a fallback, ensure environment inheritance is preserved:
const DETACHED_TRAY_HOST_LAUNCHER = [
"$startInfo = New-Object System.Diagnostics.ProcessStartInfo",
"$startInfo.FileName = $env:OCX_TRAY_HOST_BUN",
"$startInfo.Arguments = $env:OCX_TRAY_HOST_ARGS",
"$startInfo.UseShellExecute = $false",
"$startInfo.CreateNoWindow = $true",
"$startInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden",
"$startInfo.EnvironmentVariables['OCX_TRAY_ENTRY_B64'] = $env:OCX_TRAY_ENTRY_B64",
"$child = [System.Diagnostics.Process]::Start($startInfo)",
"if ($null -eq $child) { throw 'Windows tray host did not start.' }",
"$child.Dispose()",
].join("; ");2. Reset Stop Signal & Guard Timer in src/tray/windows-tray.ps1
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
try { [System.Windows.Forms.Application]::EnableVisualStyles() } catch { }
# Reset stop event on startup in Run mode
$stopEvent = New-Object System.Threading.EventWaitHandle($false, [System.Threading.EventResetMode]::AutoReset, "Local\OpenCodexTrayStop-$stableHash", [ref]$stopEventCreated)
if ($Mode -eq "Stop") {
[void]$stopEvent.Set()
$stopEvent.Dispose()
exit 0
} else {
[void]$stopEvent.Reset()
}
# Guard the timer tick
$timer.add_Tick({
try {
if ($stopEvent.WaitOne(0)) {
[System.Windows.Forms.Application]::Exit()
return
}
Update-TrayState
} catch {
Write-ActionLog "timer tick failed: $($_.Exception.Message)"
}
})Reproduction
- On a Windows machine, run:
ocx tray install - Observe immediate output:
Windows tray: installed and running - Wait 5-10 seconds and run:
ocx tray status - Notice status reports:
Windows tray: installed, not currently running - Observe taskbar tray icon: moving the cursor over or right-clicking the icon causes it to disappear instantly.
Version
2.34.0
Operating system
Windows 11 (x64)
Provider and model
N/A (OS-level CLI/tray packaging issue)
Logs or error output
PS C:\Users\Alpha> ocx tray install
Windows tray: installed and running
PS C:\Users\Alpha> ocx tray status
Windows tray: installed and running
PS C:\Users\Alpha> ocx tray status
Windows tray: installed, not currently runningScreenshots and supporting files
No response
Redacted configuration
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
lidge-jun commentedon Aug 28, 2026
리뷰 · 우선순위 70 / 80
이슈는 Windows에서
ocx tray install이 “installed and running”이라고 말한 뒤 약 3초 만에 트레이가 죽고,ocx tray status는 not running, 작업표시줄에는 ghost 아이콘만 남는다는 보고다. 작성자가 적은 원인 두 개는 지금dev(fe063d1) 코드와 맞춰 보면 설득력 있다.첫째,
src/tray/windows.ts의DETACHED_TRAY_HOST_LAUNCHER(대략 505–514행)는$startInfo.UseShellExecute = $true로 Bun__tray-host를 띄운다. .NET에서 UseShellExecute=true면 부모 env가 child에 안 넘어가는 경우가 있어, 같은 함수가 env에 넣는OCX_TRAY_ENTRY_B64가 host에 도달하지 않으면parseTrayHostEntry()(544행 근처)가Missing tray host entry로 바로 죽는다. 같은 폴더의windows-tray.ps1Start-OcxCommand는 이미UseShellExecute = $false로 env를 복사한다. 둘째,windows-tray.ps1은 named eventLocal\OpenCodexTrayStop-<hash>를 만들고 Timer 간격이 3000ms다. install/reinstall 경로의signalTrayStop()이 남긴 signaled 상태를 첫 Tick이 소비하면, 방금 띄운 트레이가 3초 만에 Exit한다. ghost 아이콘은 WinForms NotifyIcon이 비정상 종료될 때 흔히 보인다.플랫폼/GUI/CLI 라벨이 붙어 있고, 최근 Windows multi-home admit(#2800)과도 같은 Windows 운영 축이다. 수정 PR이 아직 없고, RCA가 파일·라인까지 짚혀 있어서 help-wanted로 열기 좋다. Preview 배포나 types/config 스플릿과는 무관하다.
라인
src/tray/windows.ts~509 (UseShellExecute = $true) - detached host 런처가 entry env를 버리도록 설계된 자리.$false+ EnvironmentVariables 복사, 또는 entry를 파일/인자로 넘기는 쪽으로 바꿔야 한다.라인
src/tray/windows.ts~544-548 (parseTrayHostEntry) - env 없으면 즉시 throw. host가 조용히 죽어서 install이 성공처럼 보이는 원인.라인
src/tray/windows-tray.ps1~57 / ~332-337 (stop event + 3s timer) - 이전 stop signal이 AutoReset으로 남아 있으면 첫 Tick에 Exit. install 직전 WaitOne으로 drain하거나 ManualReset+명시 Clear가 필요하다.경로
ocx tray install성공 메시지 - host/ps1이 살아 있는지 heartbeat를 확인하기 전에 “running”을 말하는 흐름이면 거짓 성공이다.경로 재현 - Windows 실기기에서 install → 3초 대기 → status/ghost 아이콘을 고정 재현 절차로 남겨야 한다.
메인테이너의 판단이 필요한 지점
help wanted/good first issue를 붙일지(RCA가 이미 상세함).너의 추천
RCA가 HEAD와 일치하니 버그로 유지하고,
UseShellExecute=$false(또는 entry 파일 전달) + install 경로에서 stop event drain + (가능하면) heartbeat 확인 후 running 표시를 한 PR로 받아라. 우선순위 높은 Windows 안정성 이슈다.이 댓글은 grok-bot이 작성했습니다
lidge-jun commentedon Aug 28, 2026
Fixed on
devby commit 42b88dc (PR #2856).The 3-second exit had a concrete cause: the detached fallback launch path went through
UseShellExecute = true, which dropped theOCX_TRAY_ENTRY_B64environment variable. The tray host then started, found no entry, threwMissing tray host entry, and died — which is exactly the "exits after 3s and fails to launch detached host" you saw.Two changes: the launch path now passes the entry through correctly instead of losing it to the shell, and the PowerShell side only resets stale stop state after it has singleton ownership, with the 3-second startup timer guarded so a slow start is not mistaken for a failed one.
Verified with
tests/windows-tray.test.ts— 21 pass, 138 assertions — driven RED against the missing launch contract, plus a mutation check (shortening the timeout to 1s makes it fail, so the assertion is real).tscandprivacy:scanclean.An honest caveat: no real Windows 11 machine was available here, so this is verified by asserting the spawn contract and options rather than by an end-to-end tray launch. If you can run
ocx tray installfrom adevbuild and report back, that would close the loop properly.This PR targeted
dev, so GitHub did not auto-close the issue — closing manually.