[Windows] 26.820.9563.0 startup hang with high CPU; multi-module preload restores launch

Open 💬 2 comments Opened Aug 27, 2026 by icearia0219
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Summary

After updating Codex Desktop to 26.820.9563.0 on Windows, launch attempts left background processes running without a visible window. The retained logs stop after two startup messages. Local diagnostic notes report sustained CPU use, and a preload hook that replaces inspector and hardware-integration modules restored startup.

This report provides the retained logs and workaround code. node:inspector is a diagnostic lead, not a confirmed root cause. It may overlap with #41056 and #41059.

Environment

  • Windows 11 Home China, 25H2, OS build 26200.9168, x64.
  • Microsoft Store/MSIX package: OpenAI.Codex, version 26.820.9563.0.
  • Executable: <package install directory>\app\ChatGPT.exe.
  • The failure was encountered after updating to this version.

The OS and installed package versions above were checked locally on 2026-08-27.

Reproduction on the affected machine

  1. Install/update the Microsoft Store OpenAI.Codex package to 26.820.9563.0.
  2. Launch Codex without the custom preload workaround.
  3. Observe that background processes start but no window appears during the failed attempts; the desktop log stops after Launching app and Appshot hotkey inactive.
  4. Launch through the local workaround described below: the window appears and the app-server connects.

This describes the observed sequence on this machine, not a minimal reproduction confirmed on other systems. The machine-specific trigger and maximum unmodified startup delay remain unknown.

Expected behavior

The desktop app should display its window and connect to its app-server without a custom preload hook. If startup cannot complete, it should report an actionable error rather than remain windowless without diagnostic progress.

Observations and retained evidence

During failed launch attempts, background processes started but no window appeared. Several retained desktop logs contain only these two startup messages. One complete failed-launch log is:

2026-08-27T06:17:08.112Z info Launching app agentRunId=null allowDebugMenu=false allowDevtools=false allowInspectElement=false buildFlavor=prod enableSparkle=false enableUpdater=true nodeEnv=undefined packaged=true platform=win32
2026-08-27T06:17:08.208Z info Appshot hotkey inactive configured=true enabled=false platform=win32

A local troubleshooting assistant's notes additionally report:

  • A main process with roughly 400–550 MB working set and dozens of threads.
  • An unresponsive process and a thread consuming approximately 5.4 seconds of CPU in a 6-second interval.
  • Reboots, an app-data reset, and uninstall/reinstall did not resolve the failure.

Those CPU measurements and troubleshooting outcomes are historical diagnostic notes, not measurements repeated for this report. I do not have a retained native stack or CPU profile, and I have not established whether an unmodified launch would eventually recover after a longer wait.

node:inspector is a lead, not a confirmed root cause

The troubleshooting notes say that a Module._load trace stopped at require("node:inspector") during startup.

Inspection of the locally extracted main bundle, app.asar/.vite/build/main-Cbf_cAoS.js, confirms that it contains a top-level require("node:inspector"). However, the complete original module-load trace was not retained. The surviving hook log contains only preload-loaded markers, so it cannot independently prove where module loading stopped.

The preserved workaround replaces all four of these modules:

node:inspector
@worklouder/device-kit-oai
node-hid
@serialport/bindings-cpp

Therefore, recovery with this hook does not establish that stubbing node:inspector alone is sufficient, or that the fault is inside Electron/V8's inspector implementation. The launcher also uses the executable directly. A controlled inspector-only / hardware-only comparison and a native stack capture are still missing.

Recovery with the workaround

The launcher temporarily adds --require=<absolute hook path> to its process-level NODE_OPTIONS, starts the packaged ChatGPT.exe directly, and restores its own previous environment value. The preload intercepts both Module._load and Module.prototype.require.

After applying the workaround, the window appeared and interaction with Codex resumed. A retained recovery-launch log progresses beyond the two-line stall:

  • At 06:20:29.930Z, the app-server connection reports initialized=true and next=connected.
  • At 06:20:30.421Z, startup critical-path phases are reported complete.
  • At 06:20:31.150Z, the renderer reports rendererWindowVisible=true.

A later running instance also has a nonzero main-window handle and Responding=True; its PID matches the surviving preload-loaded markers.

This is an unsupported diagnostic workaround, not a verified permanent fix. It substitutes incomplete implementations of inspector and hardware APIs, can affect features that use them, and has not been validated for all app functionality or future updates. I have not restarted the now-working app to repeat the failure.

<details>
<summary>Preserved preload hook (original local-path logging and comments removed)</summary>

The code below preserves the module replacement behavior of the local hook. Only comments and the file-based preload marker were removed for privacy; this sanitized presentation was not separately launch-tested.

const Module = require('node:module');
const originalLoad = Module._load;
const originalRequire = Module.prototype.require;
const noop = () => {};

const fakeInspector = Object.freeze({
  Session: class {
    connect() {}
    disconnect() {}
    post() {}
    on() {}
    once() {}
    removeListener() {}
    removeAllListeners() {}
  },
  url: () => '',
  console: Object.freeze({
    error: noop, warn: noop, info: noop, log: noop, debug: noop,
  }),
  waitForDebugger: noop,
  open: noop,
  close: noop,
});

class DisabledDeviceDiscovery {
  findWLDevices() {
    return [];
  }
}

class DisabledDeviceComm {
  onConnectionEvent() {
    return noop;
  }
  async connect() {}
  async disconnect() {}
}

class DisabledDeviceApi {
  onHidReceived() {
    return noop;
  }
  onJoystickMove() {
    return noop;
  }
  async sendLightingConfig() {
    return false;
  }
  async sendThreadsLighting() {
    return false;
  }
  async getDeviceStatus() {
    return { batteryPercentage: null, isCharging: null };
  }
}

const disabledDeviceKit = Object.freeze({
  ConnectionEventType: Object.freeze({
    CONNECTED: 'connected',
    DISCONNECTED: 'disconnected',
    ERROR: 'error',
  }),
  DeviceType: Object.freeze({ Project2077: 'Project2077' }),
  OAILightingEffect: Object.freeze({ off: 0, breath: 1, solid: 2, snake: 3 }),
  RPCApiOAI: DisabledDeviceApi,
  WLDeviceCommImpl: DisabledDeviceComm,
  WLDeviceDiscovery: DisabledDeviceDiscovery,
});

class FakeHIDDevice {
  read() {
    return undefined;
  }
  write() {
    return 0;
  }
  close() {}
  pause() {}
  resume() {}
  setNonBlocking() {}
  getFeatureReport() {
    return Buffer.alloc(0);
  }
  sendFeatureReport() {
    return 0;
  }
  on() {
    return this;
  }
  once() {
    return this;
  }
  removeListener() {
    return this;
  }
  removeAllListeners() {
    return this;
  }
  emit() {
    return false;
  }
  addListener() {
    return this;
  }
}

const fakeHid = Object.freeze({
  HID: FakeHIDDevice,
  devices: () => [],
  setDriverType: noop,
  getDeviceList: () => [],
});

const fakeSerialportBindings = Object.freeze({
  list: async () => [],
  open: async () => {
    throw new Error('disabled by codex workaround hook');
  },
  SerialPortBinding: class {
    open() {
      return Promise.reject(new Error('disabled by codex workaround hook'));
    }
    close() {
      return Promise.resolve();
    }
    read() {
      return Promise.resolve(Buffer.alloc(0));
    }
    write() {
      return Promise.resolve();
    }
    update() {
      return Promise.resolve();
    }
    set() {
      return Promise.resolve();
    }
    get() {
      return Promise.resolve({});
    }
    flush() {
      return Promise.resolve();
    }
    drain() {
      return Promise.resolve();
    }
  },
});

const stubTargets = {
  'node:inspector': fakeInspector,
  '@worklouder/device-kit-oai': disabledDeviceKit,
  'node-hid': fakeHid,
  '@serialport/bindings-cpp': fakeSerialportBindings,
};

Module._load = function (request, parent, isMain) {
  if (Object.prototype.hasOwnProperty.call(stubTargets, request)) {
    return stubTargets[request];
  }
  return Reflect.apply(originalLoad, this, [request, parent, isMain]);
};

Module.prototype.require = function (request) {
  if (Object.prototype.hasOwnProperty.call(stubTargets, request)) {
    return stubTargets[request];
  }
  return Reflect.apply(originalRequire, this, [request]);
};

</details>

Triage request

Could you confirm whether this shares a cause with the startup delay in #41056, and advise on a safe way to collect a native stack or module enter/exit timing if the unmodified launch fails again?

The reported active CPU consumption differs from the zero-CPU, one-thread suspended-process signature in #38843 / #40972, but does not prove an unrelated cause. The hardware stubs also mean the HID-related paths in #33780 / #33912 cannot yet be ruled out. #41059 reports another same-version, two-line-log startup failure.

This may be a duplicate of an existing startup report. The additional evidence here is the preserved multi-module preload workaround and the distinction between retained logs and unverified diagnostic notes; I am not claiming a confirmed new inspector bug.

View original on GitHub ↗

2 Comments

github-actions[bot] contributor · 1 day ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #41056
  • #41059

Powered by Codex Action

doupier-AI · 1 day ago

真服了这个codex,毛病那么多