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

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

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.
  1. 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.

View original on GitHub ↗

5 Comments

github-actions[bot] contributor · 21 days ago

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

  • #37013
  • #37180

Powered by Codex Action

KEYLEDGER · 19 days ago

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.

  1. 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.

<details>
<summary>Drop-in <code>AGENTS.md</code> block</summary>

````markdown

Computer Use on Windows: refresh the exec-bound helper

Initialize Sky once per fresh node_repl kernel:

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:

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\

````

</details>

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}).

  1. In a second Node REPL call, close the transport and repeat state capture using that same

returned Window object.

  1. 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.

zhivovmik-hash · 18 days ago

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.

rayb-b · 17 days ago

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.

jiangyuShiro · 17 days ago

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.