macOS Computer Use leaks SkyComputerUseClient turn-ended helpers

Open 💬 1 comment Opened Jun 20, 2026 by llamallamaredpajama

Codex Computer Use leaks SkyComputerUseClient turn-ended helper processes on macOS

Summary

Codex Desktop / Computer Use on macOS leaves SkyComputerUseClient helper processes running after turns complete. The leaked helpers include orphaned processes whose parent is PID 1 (launchd) and nested chains of:

SkyComputerUseClient turn-ended --previous-notify <json>
  /bin/bash .../codex-notify.sh turn-ended -- SkyComputerUseClient turn-ended <json>
    SkyComputerUseClient turn-ended <json>

This accumulates over time and survives normal Codex turns/sessions. The command payload contains turn metadata, so examples here are redacted.

Environment

  • Host: macOS 27.0, build 26A5353q, arm64
  • Codex.app currently installed: 26.616.32156, build 4157
  • Originally observed version from the report: 26.608.12217
  • codex CLI: codex-cli 0.130.0
  • Computer Use bundle: ~/.codex/computer-use/Codex Computer Use.app
  • Computer Use bundle version: CFBundleShortVersionString=1.0, CFBundleVersion=829
  • Service executable: .../Codex Computer Use.app/Contents/MacOS/SkyComputerUseService
  • Client executable: .../SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient

Reproduction

  1. Enable/use Codex Desktop Computer Use or any Codex path that installs the SkyComputerUseClient turn-ended notifier.
  2. Run several Codex turns or Computer Use actions.
  3. Check helper counts and parentage:
ps -Ao ppid,comm | awk '/SkyComputerUseClient/{print $1}' | sort | uniq -c | sort -rn
ps -Ao comm | grep -c SkyComputerUseClient
ps -Ao etime,pid,ppid,comm | grep SkyComputerUseClient | sort -r | head
ps -Ao pid,ppid,etime,comm | grep -E 'SkyComputerUseService|MacOS/Codex$'

Expected

After the relevant Codex turn/session ends, all per-turn SkyComputerUseClient helpers should exit. If the service exits, it should terminate and/or reap all children it spawned. A broken --previous-notify command should not leave the client process alive indefinitely.

Actual

Helpers remain alive for hours/days. On this machine, a previous run observed 1,422 simultaneous SkyComputerUseClient processes. In the current reproduction before cleanup:

  • ps -Ao comm | grep -c SkyComputerUseClient returned 22.
  • ps -Ao ppid,comm | awk '/SkyComputerUseClient/{print $1}' ... showed 7 clients parented by PID 1.
  • The oldest current helpers were over 02:13:00 old.
  • The service process was alive as a child of Codex:
/Applications/Codex.app/Contents/MacOS/Codex
  ~/.codex/computer-use/Codex Computer Use.app/Contents/MacOS/SkyComputerUseService

Unified logs showed turn-end events with no active lock-screen threads while helper processes remained:

SkyComputerUseService: Received lock-screen turn end ... activeThreadCount=0 autoUnlockedThreadCount=0

Evidence

  • Info.plist identifies the service as com.openai.sky.CUAService and the nested client as com.openai.sky.CUAService.cli.
  • Entitlements show both service and client share the 2DC432GLL2.com.openai.sky.CUAService app group.
  • The bundled Computer Use plugin launches the MCP server via:
{
  "command": "./Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient",
  "args": ["mcp"]
}
  • The leaked process command lines were turn-ended notifier invocations, not active UI interaction commands.
  • The local ~/.codex/config.toml had been rewritten to:
notify = [ "SkyComputerUseClient", "turn-ended", "--previous-notify", "[...codex-notify.sh..., ...SkyComputerUseClient..., turn-ended]" ]

This creates a self-wrapped notifier chain when an existing wrapper already preserves the original SkyComputerUseClient turn-ended notifier.

Probable root cause

There are two lifecycle gaps:

  1. SkyComputerUseClient turn-ended can block indefinitely after spawning/invoking the previous notifier chain. Its parent exits or detaches, leaving the helper reparented to launchd.
  2. The Computer Use service / installer wraps the existing Codex notify command without detecting that the previous notifier chain already includes SkyComputerUseClient turn-ended, creating a nested self-wrapper. The service also does not reap stale turn-ended helpers after turn completion.

Local mitigation installed

I installed a local, age-filtered mitigation:

  • Reset ~/.codex/config.toml to call the local notify wrapper first, then the original SkyComputerUseClient turn-ended.
  • Hardened ~/.config/dev-knowledge/hooks/codex-notify.sh:
  • strips recursive SkyComputerUseClient --previous-notify chains,
  • runs the original notifier with a bounded timeout (DK_CODEX_NOTIFY_ORIGINAL_TIMEOUT_SECONDS, default 20),
  • logs timeout/recursion warnings explicitly.
  • Added /Users/jeremy/.local/bin/codex-computer-use-reaper.
  • Default threshold: 3600 seconds.
  • Matches only the exact SkyComputerUseClient executable path.
  • Redacts turn JSON in logs.
  • Added and loaded LaunchAgent:
  • /Users/jeremy/Library/LaunchAgents/com.jeremy.codex-computer-use-reaper.plist
  • Runs at load and every 300 seconds.

Verification after mitigation:

  • Dry-run found 16 helpers, 14 older than 3600 seconds, 2 skipped as fresh.
  • Real run reaped 14 stale helpers and left the 2 fresh helpers untouched.
  • launchctl print gui/501/com.jeremy.codex-computer-use-reaper succeeds and reports interval 300 seconds.
  • Wrapper timeout test exits 124 when a notifier exceeds the configured timeout; pass-through notifier test exits 0.

Requested upstream fix

  • Make SkyComputerUseClient turn-ended always exit after notifying the service and/or invoking --previous-notify, with a bounded timeout for the previous notifier.
  • Detect and reject recursive --previous-notify chains that already include SkyComputerUseClient turn-ended.
  • Ensure SkyComputerUseService tracks all spawned helper PIDs and tears them down on turn end/service exit.
  • Add a stale-helper guard in the service: cap concurrent helpers and terminate old turn-ended helpers safely.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗