After a Windows update, ChatGPT/Codex Desktop may appear to start without ever showing a window. Task Manager shows several responsive ChatGPT.exe processes, while %LOCALAPPDATA%\OpenAI\Codex\runtimes\cua_node fills with .staging-* directories.
It is tempting to blame a crashed Electron renderer, corrupted user data, or broken MSIX permissions. In this investigation, none of those was the root cause. The application was synchronously copying thousands of Application Protected files out of WindowsApps before creating its UI. A normal file copy failed for each protected source file, and the client then retried that file through a slower byte-stream fallback.
The short version is: Windows' MSIX protection was working as designed; the defect was in Codex Desktop's runtime relocation strategy and startup sequencing.
Environment:
OpenAI.Codex 26.901.6511.0on Windows 10 Enterprise build 19045. All machine names, user names, and SIDs in this article are anonymized.
Symptoms
The post-update behavior combined several unusual signals:
- Clicking the app produced no visible UI.
- Task Manager showed roughly five or six
ChatGPT.exeprocesses. - The processes reported
Responding=True. -
MainWindowHandlewas zero. -
EnumWindowsfound no matching top-level window. - Every launch created another
.staging-<hash>-<random>directory. - A staging directory contained
bin\node.exe, but notbin\node_repl.exeormanifest.json. - After a long delay, the UI would sometimes appear without any other intervention.
That last observation mattered most. The application was not consistently crashing; it was blocked in a long startup phase that eventually completed.
Start with evidence, not destructive repair
The investigation deliberately avoided:
- uninstalling or reinstalling the app;
- resetting application data;
- deleting the whole
%LOCALAPPDATA%\OpenAI\Codextree; - taking ownership of WindowsApps;
- granting permissions to
Everyone; - disabling Windows security features;
- deleting staging directories while they were still changing.
Microsoft documents WindowsApps as a protected MSIX package location. Installed package files are read-only at runtime, while application state is stored separately. That boundary is expected and should not be bypassed: MSIX containerization overview.
Package and update state
The installed package was:
Name: OpenAI.Codex
Version: 26.901.6511.0
PackageFamilyName: OpenAI.Codex_2p2nqsd0c76g0
Its apparent install path was:
C:\Program Files\WindowsApps\OpenAI.Codex_26.901.6511.0_x64__2p2nqsd0c76g0
That directory was a junction to the real package volume:
D:\WindowsApps\OpenAI.Codex_26.901.6511.0_x64__2p2nqsd0c76g0
The package manifest declared a full-trust desktop application and excluded %LOCALAPPDATA%\OpenAI from filesystem virtualization. The startup path therefore involved a real relocation:
D:\WindowsApps\...\app\resources\cua_node
↓ runtime relocation
%LOCALAPPDATA%\OpenAI\Codex\runtimes\cua_node\<hash>
Microsoft Store telemetry showed the update completing successfully with HResult=0. Application, AppXDeployment, and AppModel logs contained no OpenAI.Codex deployment or activation errors, so AppX re-registration was not justified.
An incomplete staging folder is not automatically corrupt
Inspection of the installed app.asar showed a relocation sequence equivalent to this pseudocode:
const staging = mkdtemp(`${runtimeRoot}/.staging-${hash}-`)
copyDirectoryRecursively(sourceRuntime, staging)
renameSync(staging, finalHashDirectory)
cleanupOldRuntimeAndMatchingStaging()
The copy is depth-first. node.exe is encountered relatively early, while node_repl.exe, npm launchers, and the root manifest.json arrive near the end.
This snapshot alone does not prove corruption:
bin\node.exe exists
bin\node_repl.exe missing
manifest.json missing
The correct questions are:
- Is the file count still increasing?
- Is the staging directory's modification time moving?
- Is the main process still alive?
- Is there an explicit relocation error?
- Does the final content-addressed directory eventually appear via rename?
The decisive evidence: normal copies from WindowsApps failed
cipher /c showed that the packaged executables carried the Encrypted attribute and that ordinary user code could not retrieve their key information. The relocated files under LocalAppData were normal, unencrypted files.
A controlled test used the same bundled Node.js runtime to copy the package's 426-byte manifest into an isolated temporary directory:
{
"copyFileSync": "error",
"code": "UNKNOWN",
"errno": -4094,
"syscall": "copyfile",
"ms": 88
}
Reading the source bytes and writing a new destination succeeded immediately:
{
"readWriteFallback": "ok",
"ms": 2
}
Windows error 6000 maps to:
The specified file could not be encrypted.
Codex Desktop 26.901.6511.0 already contains a compatibility fallback for both Windows errno 6000 and Node/libuv's UNKNOWN / -4094. When copyFileSync fails, it retries with readFileSync followed by writeFileSync.
The performance bug is the granularity: every protected file pays for a failed normal copy before the fallback runs.
The runtime contained 4,684 files. At 88 milliseconds of failed-copy overhead per file:
4,684 × 88 ms ≈ 412 seconds ≈ 6.9 minutes
Add actual reads and writes, hashing, directory creation, and the final rename, and the measured relocation time was about nine minutes. That matched the delayed UI almost exactly.
This also ruled out EPERM. The locally reproduced error was UNKNOWN / -4094, and byte-stream reads remained successful.
Why repeated clicks created multiple staging folders
AppModel events recorded several application activations within a short period. A fresh process could:
- check for the final hash directory;
- see that it was missing or incomplete;
- create a new random staging directory;
- restart the entire 338 MB copy.
The in-memory relocation cache is process-local, and a later process does not resume an incomplete staging directory. Repeated launches or early termination can therefore multiply both I/O and per-file failure overhead.
The official OpenAI repository contains several closely matching public bug reports:
- 26.901.1978.0: no UI, incomplete staging, and missing node_repl.exe
- MSIX relocation failure with UNKNOWN/-4094 and ERROR_ENCRYPTION_FAILED
- Repeated Application Protected relocation regressions across updates
These reports point to the Windows relocation path in the client, not an ordinary ACL problem on one machine.
Proving that the runtime finalized correctly
One launch was left running. The runtime wrote files continuously from 16:25:03 until 16:34:17, then atomically appeared as:
%LOCALAPPDATA%\OpenAI\Codex\runtimes\cua_node\b474a88d5d105afa
Source and destination were compared:
| Check | Package source | Final runtime | Result |
|---|---|---|---|
| File count | 4,684 | 4,684 | Match |
| Total bytes | 337,646,562 | 337,646,562 | Match |
| Missing relative paths | — | 0 | Pass |
| Extra relative paths | — | 0 | Pass |
| Size mismatches | — | 0 | Pass |
SHA-256 hashes also matched for the three files used to identify and validate the runtime:
manifest.json
bin\node.exe
bin\node_repl.exe
The manifest described the expected Windows x64 runtime:
{
"platform": "windows",
"arch": "x64",
"target": "windows-x64",
"node_version": "24.19.0",
"node_path": "bin/node.exe",
"node_modules": "bin/node_modules",
"node_repl_path": "bin/node_repl.exe"
}
Node.js, Node REPL, and the CUA helper were readable or executable, and the important executables had valid signatures. Existing configuration now referenced the current hash for Node, Node REPL, node_modules, and the computer-use helper.
After finalization, the client removed matching failed staging folders and the previous runtime. No application directory was manually deleted or overwritten.
Process-tree verification
Once relocation finished, the process tree progressed beyond the basic Chromium bootstrap processes:
ChatGPT.exe main process
├─ ChatGPT.exe crashpad / utility / GPU / renderer workers
├─ ChatGPT.exe network and storage services
└─ codex.exe app-server
├─ node_repl.exe
├─ codex-code-mode-host.exe
└─ codex-command-runner.exe
Some renderer and utility processes use stricter sandbox tokens, so a separate diagnostic account cannot always read their complete command lines. That is expected process isolation. A visible UI, high-memory renderer children, a running app-server, and an active Node REPL confirmed that both rendering and backend startup were healthy.
Cold-start validation
The app was fully exited and launched again:
- The new main process started at 17:06:42.
- The Codex app-server started at 17:06:47.
- The UI appeared within the normal startup window.
-
cua_nodestill contained onlyb474a88d5d105afa. - The staging count remained zero.
- The runtime's last-write time remained 16:34:17.
- None of the 4,684 files was recopied.
- Application, AppX, and AppModel error counts remained zero.
This proved that the local state was repaired and that later launches reused the finalized runtime correctly.
Interpreting codex doctor
codex doctor was also run. CLI installation, runtime, disk, Git, and search-tool checks passed.
Desktop, authentication, and connectivity warnings came from running the command under an isolated diagnostic identity with restricted network access. That identity could not inspect the primary user's AppX registration or private package LocalCache. Those warnings described the diagnostic sandbox, not the running desktop application.
Diagnostic output must always be interpreted together with its user identity, inherited environment, filesystem access, and network policy.
Why reinstalling or changing permissions was the wrong fix
The evidence showed that:
- the package payload was complete;
- the Store update succeeded;
- package and executable signatures were valid;
- LocalAppData permissions were correct;
- there was no
EPERM, code-integrity block, quarantine, or application crash; - relocation could finish and atomically finalize.
Reinstalling, resetting data, changing WindowsApps ACLs, or disabling security controls would not remove the per-file fallback cost. Those actions could instead erase login state, damage package integrity, or weaken system security.
What to do after the next update
If a future release keeps the same cua_node content hash, the existing runtime will be reused.
If the hash changes, the first launch may remain slow until OpenAI changes the relocation implementation:
- Launch the app once after updating.
- Do not repeatedly click the app icon.
- Do not terminate the process while staging file counts and timestamps are still moving.
- Allow roughly 10–15 minutes for first-time materialization.
- Investigate further only if staging stops changing for an extended period or a concrete error is logged.
- Never delete a content-addressed runtime that has already passed integrity checks.
A robust client-side fix should:
- use a decrypted-destination copy mode immediately for WindowsApps sources;
- avoid making every file fail once before falling back;
- ship the many small files as an archive and extract it once;
- move materialization off the UI-critical startup path or show progress;
- add an inter-process relocation lock and resumable staging;
- surface the failing file, operation, and Win32 error in the UI.
Final takeaway
The failure looked like “Electron started but never created a renderer.” In reality, the renderer was waiting behind a synchronous runtime relocation that incurred 4,684 Application Protected copy failures.
No reinstall, application reset, ownership change, or manual runtime overwrite was necessary. Once one relocation completed, Codex created the correct content-addressed directory, cleaned its staging residue, and reused the result on the next cold start.
For similar incidents, build a timeline before deleting anything: Did the package update succeed? Is staging still growing? Are the source files protected? What is the actual copy error? Did the final directory appear atomically? Does a cold start reuse it? That evidence separates expected Windows security behavior from the client bug that actually needs fixing.
Top comments (0)