Skip to content

[Bug]: agent-device wrapper doesn't set ELECTRON_RUN_AS_NODE, so Codex agent calls launch the desktop app and SIGABRT ("T3 Code quit unexpectedly") #18182

Open

Description

@gitkamaal

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/desktop

Summary

The device-tool wrapper ~/.t3/userdata/device/bin/agent-device runs the Electron app binary as a script interpreter but never sets ELECTRON_RUN_AS_NODE=1. The wrapper only gets Node mode if the variable is inherited from its caller.

When a Codex agent calls the wrapper through its shell tool, the variable is missing. Codex's shell_environment_policy (inherit = "core") strips it. The binary then starts up as a full Electron desktop app instead of Node:

  • Sandboxed command: NSApplication init can't reach WindowServer and calls abort(). The command exits with code 134 and macOS shows a "T3 Code (Nightly) quit unexpectedly" crash dialog. It looks like T3 crashed, but the main window is fine.
  • Escalated (unsandboxed) command: a second T3 desktop instance starts up ("app ready", "backend ready"). It then fails on IndexedDB/…leveldb/LOCK because the real instance holds the lock.

The same problem exists one level down: agent-device-launcher.mjs spawns the app binary again with { ...process.env } and doesn't set ELECTRON_RUN_AS_NODE there either.

This gets more likely once #16456 is fixed: removing ELECTRON_RUN_AS_NODE from provider sessions would break every agent call to agent-device.

Generated wrapper (as written by T3)

~/.t3/userdata/device/bin/agent-device:

#!/bin/sh
exec '/Applications/T3 Code (Nightly).app/Contents/MacOS/T3 Code (Nightly)' '~/.t3/userdata/device/bin/agent-device-launcher.mjs' "$@"

~/.t3/userdata/device/bin/agent-device-launcher.mjs (excerpt):

const env = { ...process.env };
delete env.AGENT_DEVICE_DAEMON_BASE_URL;
delete env.AGENT_DEVICE_DAEMON_AUTH_TOKEN;
delete env.AGENT_DEVICE_CONFIG;
const child = spawn("/Applications/T3 Code (Nightly).app/Contents/MacOS/T3 Code (Nightly)", [".../agent-device/0.21.23/node_modules/agent-device/bin/agent-device.mjs", ...args], { stdio: "inherit", env });

Steps to reproduce

Deterministic, no agent needed (macOS, from any terminal):

W=~/.t3/userdata/device/bin/agent-device
P='(version 1)(allow default)(deny mach-lookup (global-name "com.apple.windowserver.active") (global-name "com.apple.CoreServices.coreservicesd") (global-name-prefix "com.apple.coreservices.launchservicesd"))'

# A) variable present: works
ELECTRON_RUN_AS_NODE=1 sandbox-exec -p "$P" "$W" help; echo "exit=$?"
# -> agent-device <command> [args] [--json] ... exit=0

# B) variable absent (what a Codex shell gets): SIGABRT + crash report
env -u ELECTRON_RUN_AS_NODE sandbox-exec -p "$P" "$W" help; echo "exit=$?"
# -> exit=134, new "T3 Code (Nightly)-<ts>.ips" in ~/Library/Logs/DiagnosticReports

# Also shows the binary isn't in Node mode: --version prints the T3 app version, not agent-device's
env -u ELECTRON_RUN_AS_NODE "$W" --version   # -> 0.0.46-nightly.20261011.2955  (expected 0.21.23)
ELECTRON_RUN_AS_NODE=1     "$W" --version    # -> 0.21.23

Real-world trigger: in a Codex (gpt-6.1-sol) thread with device access, the agent runs any agent-device … command from its shell tool (for example agent-device help or agent-device snapshot -i --platform ios --udid … --config … --session …).

Expected behavior

agent-device always runs as Node, whatever environment the caller passes, and never starts the desktop app or triggers a macOS crash dialog.

Actual behavior

Every agent-initiated agent-device call from a Codex shell exits with code 134. Each one creates a crash report and a "quit unexpectedly" dialog, which makes it look like T3 is crashing repeatedly. Escalated calls start a second desktop instance instead.

Evidence from one night. Every crash report matches a Codex CommandExecution with exit_code: 134 to the second:

Crash report (local, EDT) Codex command (UTC) Command
00:09:43 04:09:43Z → 134 agent-device open --help --platform ios --udid … --config … --session …
00:12:42 04:12:42Z → 134 agent-device install com.example.app …/Debug-iphonesimulator/App.app --platform ios --udid …
05:03:38 09:03:38Z → 134 agent-device --help
05:55:29 09:55:29Z → 134 agent-device help
06:05:11 10:05:09Z → 134 agent-device snapshot -i --platform ios --udid …
06:05:58 (manual repro B above) → 134 agent-device help

All of these crash reports have parentProc: codex (codex app-server), except the manual repro, whose parent is zsh. Each process lived about 150 ms.

Escalated run of the same command (codex log, 05:55:58 local), showing a second desktop instance starting up:

[05:55:58.956] INFO (#4): runtime logging configured  scope: desktop  component: desktop-startup
[05:55:58.958] INFO (#4): app ready  scope: desktop  component: desktop-startup
...
ERROR:content/browser/indexed_db/instance/leveldb/backing_store.cc:237] Failed to open LevelDB database from ~/Library/Application Support/t3code-v2/IndexedDB/t3code_app_0.indexeddb.leveldb, IO error: .../LOCK: No further ...

Suggested fix

Don't depend on the caller's environment:

  1. In the generated agent-device shell wrapper:
    ELECTRON_RUN_AS_NODE=1 exec '<app binary>' '<launcher.mjs>' "$@"
  2. In agent-device-launcher.mjs, set env.ELECTRON_RUN_AS_NODE = "1" on the spawned child.
  3. Check any other generated shims that use the app binary as an interpreter (for example the expo-device-hub client) for the same issue.

Verified that adding (1) alone fixes the repro in the same sandbox:

env -u ELECTRON_RUN_AS_NODE sandbox-exec -p "$P" sh -c 'ELECTRON_RUN_AS_NODE=1 exec "/Applications/T3 Code (Nightly).app/Contents/MacOS/T3 Code (Nightly)" ~/.t3/userdata/device/bin/agent-device-launcher.mjs --version'
# -> 0.21.23, exit 0

Impact

Major degradation or frequent failure

Version or commit

T3 Code (Nightly) 0.0.46-nightly.20261011.2955; also seen on 0.0.46-nightly.20261010.2948. agent-device 0.21.23.

Environment

macOS 27.0 (26A428), Apple Silicon (arm64). Provider: Codex app-server (gpt-6.1-sol), ~/.codex/config.toml with [shell_environment_policy] inherit = "core". Claude Code threads are unaffected because their shell inherits ELECTRON_RUN_AS_NODE=1.

Logs or stack traces

Faulting thread (identical in every report, including the manual repro):

EXC_CRASH (SIGABRT)  asi: libsystem_c.dylib: "abort() called"
0  libsystem_kernel.dylib  __pthread_kill
1  libsystem_pthread.dylib pthread_kill
2  libsystem_c.dylib       abort
3  HIServices              ___RegisterApplication_block_invoke
4  libdispatch.dylib       _dispatch_client_callout
5  libdispatch.dylib       _dispatch_once_callout
6  HIServices              _RegisterApplication
7  HIServices              GetCurrentProcess
8  AppKit                  -[NSMenuBarPresentationInstance _getAggregateUIMode:withOptions:]
9  AppKit                  _NSGetAggregateUIMode
10 AppKit                  -[NSMenuBarPresentationInstance _isVisible]
11 AppKit                  +[NSMenuBarPresentationInstance _isMenuBarVisible]
12 AppKit                  _NSInitializeAppContext
13 AppKit                  -[NSApplication init]
14 AppKit                  +[NSApplication sharedApplication]
15 Electron Framework      node::PrincipalRealm::inspector_enable_network_tracking() const
16 Electron Framework      ...
17 Electron Framework      ElectronInitializeICUandStartNode

Workaround

Add ELECTRON_RUN_AS_NODE = "1" under [shell_environment_policy.set] in ~/.codex/config.toml (untested; it should restore the variable for Codex shells). Or call the wrapper as ELECTRON_RUN_AS_NODE=1 agent-device …. Note that T3 regenerates the wrapper files, so editing them by hand doesn't stick.

Related: #16456 (the opposite direction: ELECTRON_RUN_AS_NODE leaking into provider sessions on Linux).

Activity

  1. gitkamaal commented on Oct 11, 2026

    @gitkamaal
    Author

    Confirmed workaround: adding ELECTRON_RUN_AS_NODE = "1" under [shell_environment_policy.set] in ~/.codex/config.toml fixes it. Inside a sandboxed codex exec shell, agent-device --version now prints 0.21.23 and exits 0, with no new crash report. Two codesign_util.cc:79 task_name_for_pid errors still print to stderr, but they are harmless. Already-running codex app-server processes only pick up the change after a restart.

  2. juliusmarminge commented on Oct 11, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Confirmed on current main. Same hole as #12926 (closed completed on 2026-10-02 without a fix landing; #13093 and #12241 stayed unmerged). This issue is the live tracker.

    On desktop Electron, resolveNodeExecutable (packages/shared/src/nodeRuntime.ts) returns process.execPath when the host is not a Node SEA — intentionally the app binary. Other desktop spawners already force Node mode (DesktopCliShim, backend child env, snapshot helpers). ensureAgentDeviceShim (apps/server/src/device/AgentDeviceShim.ts) does not: it still writes

    #!/bin/sh
    exec '<Electron app binary>' '<state>/device/bin/agent-device-launcher.mjs' "$@"

    and the launcher re-spawns that binary with { ...process.env } only. No ELECTRON_RUN_AS_NODE=1 in the POSIX wrapper, the Windows .cmd, or the launcher child env.

    When a Codex shell runs the shim, shell_environment_policy inherit = "core" strips any inherited flag. Sandboxed calls hit NSApplication init → abort() (exit 134, "T3 Code quit unexpectedly"). Escalated calls start a second desktop instance and fight the IndexedDB lock. Claude Code threads look fine only because they keep the inherited flag.

    #16456 / #16679 (stop leaking ELECTRON_RUN_AS_NODE into provider sessions) make this worse if they land first — every agent call to agent-device would then depend on the shim setting the flag itself.

    Fix: set ELECTRON_RUN_AS_NODE=1 in the generated POSIX/cmd wrappers and in agent-device-launcher.mjs's spawn env (the #13093 / #12241 shim half). Add a regression that runs the generated shim through a fake desktop executable that refuses to start without Node mode. Temporary workaround: ELECTRON_RUN_AS_NODE = "1" under [shell_environment_policy.set] in ~/.codex/config.toml, or prefix the command; hand-editing the regenerated wrapper does not stick.

  3. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 11, 2026
  4. WesleyYJiang commented on Oct 13, 2026

    @WesleyYJiang

    Another consequence of this one, worse than the crash: the stray desktop instance can re-point the machine's T3 Connect managed tunnel.

    What we saw (macOS, Nightly 0.0.46-nightly.20261012):

    • A Codex thread ran ~/.t3/userdata/device/bin/agent-device install … from a zsh -lc shell. The env had no ELECTRON_RUN_AS_NODE, because Codex's shell_environment_policy inherit = "core" drops it.
    • A full second desktop instance booted on the same userdata. Port 3773 was taken, so it picked 3774 ("selected backend port via sequential scan"), ran environment.cloud.activateManagedTunnel with origin http://127.0.0.1:3774, got before-quit about 35 s later and exited.
    • The tunnel config is shared, so the main instance's cloudflared kept forwarding relay traffic to 127.0.0.1:3774 (dial tcp 127.0.0.1:3774: connect: connection refused, relay host returned 502). Every remote client (desktop peers, iOS) saw the environment as unreachable for hours, while the main app looked healthy.
    • Recovery without a UI re-link: SIGTERM the main server's cloudflared. The supervisor's exit handling runs recoverManagedCloudTunnel with the main server's own origin and the relay host returns 200 again.

    Workaround on our side: [shell_environment_policy.set] ELECTRON_RUN_AS_NODE = "1" in ~/.codex/config.toml.

    Suggested fixes: have the generated wrapper set the variable itself (exec env ELECTRON_RUN_AS_NODE=1 …). Separately, it may be worth making activateManagedTunnel refuse, or at least warn, when another live instance already owns the confirmed origin on the same userdata.

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

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions