Skip to content

Computer Use on Windows: get_window_state fails with node_repl exec context not found #37281

Open

Description

@nickcybela

Summary

Computer Use can discover an allowed Windows desktop app and its window, but it cannot inspect or control the returned window. Every state capture fails immediately with:

Error: node_repl exec context not found

Environment

  • Windows desktop
  • ChatGPT/Codex desktop app build: 26.730.61639
  • Bundled Computer Use plugin build: 26.730.61639
  • Target tested: Adobe Premiere Pro 2026
  • Computer Use invoked explicitly with @Computer

Steps to reproduce

  1. Install and enable the bundled Computer Use plugin.
  2. Open a Windows desktop app.
  3. Invoke @Computer and ask it to inspect/control that app.
  4. Initialize @oai/sky through the bundled node_repl.
  5. Call sky.list_apps().
    • The target app and exactly one window are returned successfully.
  6. Rehydrate the exact returned window with sky.get_window({ id, app }), then call either:
    • sky.get_window_state({ window, include_screenshot: true, include_text: false }), or
    • sky.get_window_state({ window, include_screenshot: false, include_text: true }).

Actual result

Both screenshot-backed and text-only state capture fail with:

Error: node_repl exec context not found

No input reaches the target app.

Troubleshooting already attempted

  • Explicitly invoked the installed Computer Use plugin with @Computer.
  • Added the exact app identifier to [computer_use.windows].always_allowed_app_ids.
  • Restarted the Codex desktop app.
  • Refreshed app/window discovery and retried once using the exact newly returned window.
  • Confirmed sky.list_apps() still succeeds after restart.
  • Confirmed nodeRepl.requestMeta exists, while the execution/elicitation context required by the Computer Use operation appears unavailable.

Expected result

sky.get_window_state() should return the selected window's screenshot or accessibility state so Computer Use can proceed.

Impact

Computer Use can enumerate Windows apps but cannot inspect or operate an already allowed target app, blocking all desktop GUI automation beyond discovery.

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    windows-osIssues related to Codex on Windows systems
    on Aug 6, 2026
  2. github-actions commented on Aug 6, 2026

    @github-actions
    Contributor
  3. KEYLEDGER commented on Aug 8, 2026

    @KEYLEDGER

    Root cause and workaround: the Computer Use helper transport is bound to the exec context that spawned it

    If you only want the fix: call await sky.transport.close() at the start of every new
    node_repl JS call, before any Computer Use work. No js_reset needed. Copy-ready
    AGENTS.md block in §9; everything else is why it happens.

    1. Summary

    node_repl exec context not found on Computer Use calls is caused by the persistent helper
    transport outliving the AsyncLocalStorage exec context in which it was created.

    The transport holds a handle to a long-lived codex-computer-use.exe child process. That
    process and its streams are async resources tagged with whichever exec first called
    request(). A callback from the helper back into the kernel then resolves against that
    originating exec. From any later exec, execState.id !== activeExecId and the kernel throws.
    (Both elicitation and image emission run through this path; §3 explains why we could not
    isolate which one is responsible.)

    A one-line workaround exists today: await sky.transport.close() at the start of each new
    JS call. js_reset is not required.

    2. Environment

    • Codex app package 26.803.5235.0, Computer Use plugin 26.803.41515
    • cua_node runtime 0.0.6/20260723162306-088049353ddc, Node 24.14.0,
      @oai/sky 0.6.2
    • Windows 11 26200

    Independently reported on build 26.730.61639 against Adobe Premiere Pro (this issue), so it is
    neither app-specific nor user-specific and has survived a version bump.

    3. Mechanism

    The guard, from node_repl.exe:

    function getCurrentExecState() {
      const execState = getAsyncExecState();
      // AsyncLocalStorage preserves the originating store for late callbacks, even
      // after the surrounding exec has already finished. Most helpers still require
      // an active exec because their results attach to the current tool call.
      if (execState.id !== activeExecId) throw new Error("node_repl exec context not found");
      return execState;
    }

    The transport, from @oai/sky/dist/.../windows/internal/helper_transport.js (deobfuscated):

    close() {
      const proc = this.#child;
      if (proc == null) return Promise.resolve();
      if (proc.exitCode != null || proc.signalCode != null) { this.#cleanup(proc); return Promise.resolve(); }
      return this.request("close", {}).catch(() => proc.kill()).finally(() => this.#cleanup(proc));
    }                                                  // #cleanup nulls #child
    
    request(method, params, opts = {}) {
      let proc = this.#child ?? this.#spawnHelper();   // <-- lazy respawn when #child is null
      this.#child = proc;
      // ... respawn again if the process has since exited ...
      return this.#send(proc, method, params, turnMeta, opts.createElicitation);
    }

    close() nulls #child; the next request() spawns a fresh helper inside the current
    exec
    , so its async resources carry the current exec's tag. That is exactly why closing
    recovers without a kernel reset.

    Why only some methods fail. In the reproduced, already-approved path, list_apps() and
    get_window() behaved as request/response operations and survived across execs.
    get_window_state() exercised the failing path — note createElicitation threaded through
    every request(), and the string failed to reply to node_repl emit_image request in
    node_repl.exe. The evidence supports a helper→kernel callback as the discriminator, although
    it does not yet isolate whether elicitation, image emission, or another callback is responsible.

    4. Why #37180 and #37281 look like different bugs

    Two throw sites share one message:

    Site Condition Symptom
    execState == null no store at all — a callback outside any exec #37180: fails on the first launch_app, in the approval/elicitation callback
    execState.id !== activeExecId store exists, belongs to a prior exec #37281: succeeds once, fails on subsequent calls

    The two reports are consistent with the same underlying defect — a helper→kernel callback
    resolving against the wrong exec — reached from two directions. We reproduced the second-row
    path; the first-row mapping remains an inference from #37180 and the two shipped throw sites.

    5. Evidence

    Runtime (Codex), all timestamps UTC:

    • Kernel itself is healthy across calls: after js_reset, list_apps() succeeded at
      22:12:32.236, and a following separate call containing only 1 + 1 returned 2 at
      22:12:36.532.
    • Not all Sky methods fail: three separate list_apps() calls across distinct execs all
      succeeded (22:12:51.011, 22:12:57.126, 22:13:02.473).
    • get_window_state() succeeded at 22:13:54.092, then failed in the next call with the context
      error. Two get_window_state() calls inside one exec both succeeded
      (22:14:27.318–22:14:27.711).
    • Failure is immediate, which rules out a waiting-based timeout/GC recovery explanation.
    • Rehydration is not a fix: get_window({id, app}) succeeded at 22:14:06.992, and
      get_window_state() on that rehydrated window still failed.
    • await import("@oai/sky") in a later exec returns the same singleton
      (sameSingleton: true); state capture still failed.
    • js_reset clears globalThis ({"typeofSky":"undefined","hasOwnSky":false}), so re-import
      is mandatory with that approach.
    • Decisive: baseline capture succeeded 22:19:16.702. Next exec:
      await sky.transport.close() then get_window_state() → succeeded 22:19:21.762 with no
      js_reset. Reusing that new transport in the following exec failed again. Closing again
      recovered at 22:19:34.821.
    • Follow-up durability probe: 12 consecutive, separate JS execs each ran
      transport.close() followed by a text-only get_window_state() against the same returned
      Notepad++ window. All 12 succeeded. No codex-computer-use.exe process existed before the
      run, exactly one existed after cycle 12, and none remained after a final transport.close().
      This found no helper-process accumulation; it is not a proof against every possible memory
      or handle leak.
    • get_window_state({include_screenshot:false, include_text:false}) cannot isolate a
      callback-free state path: the API rejects it with get_window_state must request include_text, include_screenshot, or both.

    Static / external (Claude):

    • Exec registrations in ~/.codex/node_repl/active_execs/ live ~1 second, fresh UUID per
      JS call, e.g. {"execId":"b625ec45-...","nodeReplPid":19300,"kernelPid":10452,...}.
    • Process topology: node_repl.exe (supervisor) → node.exe (V8 kernel) →
      codex-computer-use.exe (helper). The helper is a child of the kernel.
    • @oai/sky's client is a module-level singleton with no invalidation path (sky.js, 778
      bytes: let n = null; ... return n || (n = create_client(...))). This is why re-import does
      not refresh it — but it is not the root cause; the transport underneath is.

    Safety / UI sanity check. The workaround still routes every supported input operation
    through the standard codex-computer-use.exe helper; it does not introduce an alternate input
    path. With transport.close() used at every exec boundary, a visible Notepad++ demonstration
    used press_key, click, and type_text, and a human observer confirmed the animated screen
    border and status pill on the physical display: Codex is using your computer / Esc to cancel. The installed helper binary contains those exact strings together with the border,
    status-pill, keyboard-hook, and overlay-rendering implementation. It also contains an explicit
    exclude display overlay from captures path, consistent with the overlay being visible on the
    physical display but absent from Computer Use screenshots returned to the model. This verifies
    that the workaround did not bypass the supported active-control transparency cue. We do not
    claim that cue is an OS-level security boundary or that it signals every passive capture. The
    official Computer Use documentation confirms the
    foreground behaviour — Computer Use "runs on the active desktop", and users should "expect
    ChatGPT to move the pointer, type, and take over the foreground". It does not currently
    describe the overlay cue, so that part was verified against the shipped binary and direct
    observation rather than the docs.

    6. Suggested fixes

    1. Recreate or rebind the helper transport per exec. Either spawn per exec, or have
      helper→kernel callbacks resolve against the current activeExecId rather than the
      originating one.
    2. Expose the escape hatch officially. transport.close() works but is reached through an
      internal property. A supported sky.reset() would make the workaround legitimate.

    The separate @oai/sky package-exports defect tracked in #27287 remains relevant to other
    Computer Use bootstrap failures, but is not part of this root cause.

    7. Workaround for users today

    // Before Computer Use work in each NEW JS call, once Sky has been imported:
    if (globalThis.sky?.transport?.close) {
      await sky.transport.close();
    }
    // Then list / select / act / capture normally. Multiple operations in this call are fine.

    Preserves globalThis, imports, and REPL state. Heavier fallback if that ever fails:
    js_reset → import + select + act + capture in one call (note js_reset clears globalThis).
    transport is currently an internal property, so this remains an unsupported workaround even
    though it is materially lighter than resetting the kernel.

    8. Also worth correcting

    sky.documentation(), which the bundled SKILL.md instructs the model to call, does not
    exist
    in @oai/sky 0.6.2 — zero occurrences in dist/. It throws
    sky.documentation is not a function, and a model left without an API reference then guesses
    parameter shapes, producing the misleading
    window.app must be a non-empty string and window.id must be an integer >= 0. The real docs
    ship on disk at @oai/sky/docs/sky-window2-api.md and in the plugin's docs/.

    9. Copy-ready Codex guidance and independent verification

    This workaround does not patch the binaries. It teaches Codex to recreate the affected helper
    transport at each Node REPL exec boundary. Per OpenAI's AGENTS.md
    documentation
    , put reusable
    global guidance in ~/.codex/AGENTS.md (%USERPROFILE%\.codex\AGENTS.md on Windows) and start
    a new Codex run/session after editing it.

    Drop-in AGENTS.md block
    ## Computer Use on Windows: refresh the exec-bound helper
    
    Initialize Sky once per fresh `node_repl` kernel:
    
    ```js
    if (!globalThis.sky) {
      const { sky } = await import("@oai/sky");
      globalThis.sky = sky;
    }
    ```
    
    Before Computer Use work in every **new** `node_repl` JS call, recreate the helper transport:
    
    ```js
    if (globalThis.sky?.transport?.close) {
      await sky.transport.close();
    }
    ```
    
    Then list/select/act/capture normally in that same call. Multiple Computer Use operations
    inside one JS call may share the transport and do not need intermediate closes.
    
    Pass `Window` objects verbatim as returned by `sky.list_apps()` or `sky.list_windows()`.
    Never rebuild a window object by hand, and do not convert its integer `id` to a string.
    
    If transport recreation fails, use the heavier fallback: `js_reset`, re-import `@oai/sky`,
    then list, select, act, and capture within one JS call. `js_reset` clears `globalThis`.
    
    `sky.transport` is currently internal API; revalidate this workaround after Codex, the
    Computer Use plugin, or `@oai/sky` updates.
    
    In `@oai/sky` 0.6.2, `sky.documentation()` is absent. Read the installed docs from dynamically
    discovered paths rather than hardcoding version/hash directories:
    
    - `%LOCALAPPDATA%\OpenAI\Codex\runtimes\cua_node\*\bin\node_modules\@oai\sky\docs\`
    - `%USERPROFILE%\.codex\plugins\cache\openai-bundled\computer-use\*\docs\`

    Verification on another affected installation:

    1. Start a new Codex run/session so the global AGENTS.md is loaded.
    2. In one Node REPL call, import Sky, close the transport if present, call list_apps(),
      select exactly one returned non-sensitive test window, and capture text-only state with
      get_window_state({window, include_screenshot:false, include_text:true}).
    3. In a second Node REPL call, close the transport and repeat state capture using that same
      returned Window object.
    4. Expected result: both captures succeed without js_reset. As an optional control on an
      affected build, omitting transport.close() from the second call reproduces
      node_repl exec context not found; adding it in the following call recovers.

    Two practical notes for implementers:

    • Calling transport.close() on a kernel where no helper has spawned yet is a no-op, not an
      error — close() returns immediately when #child is null (see §3). So the guarded call is
      safe to run unconditionally at the top of every JS exec, including the first.
    • Redact before writing accessibility output. include_text: true returns whatever is on
      screen, including secrets in an open editor buffer. During our own reproduction, an
      unpredictable Ctrl+Tab landed on a config file and the accessibility tree surfaced a
      credential-like value. Anything written back through nodeRepl persists in plaintext in the
      session transcript under ~/.codex/sessions/ and is sent as model context. Use a window with
      no sensitive content, avoid unpredictable navigation mid-capture, and filter document_text
      rather than dumping it wholesale.

    This is sufficient to replicate the practical workaround. The process-count durability probe
    in §5 is additional validation, not a required setup step.


    Method and provenance

    This analysis was produced by two AI models working in parallel on the affected machine,
    coordinating directly through a shared file-backed message queue. Each had tools the other
    lacked, and the split was load-bearing rather than cosmetic.

    Claude Opus 5 supplied the static, external, and issue-research lane. It extracted both
    node_repl.exe guard paths behind the shared error string; deobfuscated the Sky singleton and
    the helper transport's close() / lazy-respawn behavior; captured live exec registrations,
    their short lifetimes, kernel PIDs, and the supervisor→kernel→helper process topology; mapped
    those findings against #37281, #37180, and the separate package-exports issue; and designed
    falsifiable discriminator tests. After the runtime finding, that transport deobfuscation
    supplied the missing mechanism: close() cleans up and nulls #child, and the next request
    spawns a helper whose async resources belong to the current exec. It also audited the report
    itself: independently verifying versions, citations, and the overlay strings in the shipped
    helper binary, qualifying an unsupported callback-specific claim, and identifying the redaction
    and first-exec no-op guidance needed for a safe public recipe.

    GPT 5.6 Sol (Codex) supplied the live-runtime and operational lane. Codex executed and
    timestamped the experiment matrix: plain-JS survival, repeated lightweight Sky calls,
    same-exec versus cross-exec state captures, window rehydration, singleton re-import, reset
    semantics, and active-exec registry inspection. Codex found the decisive lower-level escape
    hatch empirically: close sky.transport, then capture state in the same new exec. It verified
    the alternating close→success / reuse→failure behavior, ran 12 successful close/respawn cycles
    without helper-process accumulation, corrected the app-package/plugin version conflation, and
    turned the finding into durable AGENTS.md guidance, a fallback path, an independent
    verification procedure, and the active-control overlay sanity check above.

    The correction record remains important. Claude initially attributed the root cause to the
    client singleton and advised Codex not to spend a cycle testing close(); Codex tested the
    lower-level transport anyway, disproving the first explanation and finding the practical
    workaround. The singleton analysis remained useful—it explains why re-importing cannot
    recover—but it was demoted from cause to contributing constraint. In the other direction,
    Claude's static follow-through converted an empirical workaround into a concrete lifecycle
    mechanism, and its adversarial review caught overclaiming and an operational privacy hazard
    before publication. Codex's experiments likewise corrected its own earlier blanket model that
    the “second Sky call” always fails by showing that lightweight methods survive.

    Neither evidence lane would have produced this report as written. The runtime experiments
    established what works and falsified plausible alternatives; the static and external evidence
    explained why it works, separated two identical error strings, and made the result actionable
    for a maintainer. We include the collaboration history as provenance, not as a substitute for
    the technical evidence above.

  4. zhivovmik-hash commented on Aug 9, 2026

    @zhivovmik-hash

    Confirmed on another affected Windows installation with the bundled Computer Use plugin 26.803.41515. Target application: Android Studio (studio64.exe).

    Reproduction matches this issue:

    • sky.list_apps() discovers exactly one Android Studio window.
    • Without the workaround, both text-only and screenshot-backed sky.get_window_state() fail with: Error: node_repl exec context not found.
    • Reinstalling the plugin, restarting the desktop app, refreshing the returned window, and retrying do not fix it.

    I also independently tested the workaround from the comment above:

    if (globalThis.sky?.transport?.close) {
      await sky.transport.close();
    }

    Calling it at the start of a new JS exec removes the exec-context error. The text-only state request then completes (Android Studio returned no accessibility tree on this machine). A screenshot-backed request advances past the original context error but then fails with a separate Windows capture error:

    IGraphicsCaptureItemInterop.CreateForMonitor failed: The specified service does not exist as an installed service. (0x80070424)
    

    So the transport-close workaround is confirmed on plugin 26.803.41515, although this installation still cannot capture Android Studio because of the subsequent Windows Graphics Capture failure. This may overlap with #37629.

  5. rayb-b commented on Aug 10, 2026

    @rayb-b

    Confirmed the transport-close workaround on another affected installation.

    Environment:

    • Codex Desktop 26.803.5235.0
    • Computer Use plugin 26.803.41515
    • @oai/sky 0.6.2

    After a full Codex restart alone, a text-only get_window_state call had still failed with node_repl exec context not found. In a new controlled run, I selected a newly created blank Notepad window from the verbatim list_windows result. At the start of the next node_repl execution I called:

    if (globalThis.sky?.transport?.close) {
      await sky.transport.close();
    }

    The single subsequent get_window_state({ include_screenshot: false, include_text: true }) call completed successfully. It returned a WindowState and did not reproduce the exec-context error. The blank Notepad target returned accessibility=null and zero screenshots, so this confirms removal of the context error but does not claim accessibility coverage for every app.

    No activation, click, keyboard, typing, scrolling, screenshot capture, binary patch, runtime modification, or helper bypass was used. The transport was closed again after the test.

  6. jiangyuShiro commented on Aug 11, 2026

    @jiangyuShiro

    This looks like the same defect as #37013, and it no longer reproduces here.

    • Codex Desktop AppX: 26.803.10989.0 x64
    • Computer Use plugin: 26.803.81509
    • @oai/sky: 0.6.6 (was 0.6.2; helper_transport.js SHA-256 changed from 6423BA83... to 7BC54C5B...)
    • Windows 10.0.26200 x64, stock install (extracted runtime matches the AppX copy byte for byte)

    With a fresh JS kernel, get_window_state() called from a node_repl/js execution after the one that produced the window handle now succeeds. A screenshot id created in one call is also still valid for sky.click() in a later independent call, so the observe-then-act workflow works end to end.

    Full test details in #37013.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appbugSomething isn't workingcomputer-usewindows-osIssues related to Codex on Windows systems

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions