Sandbox blocks asyncio self-pipe wakeups: call_soon_threadsafe/to_thread/anyio hang with PermissionError (EPERM)

Open 💬 1 comment Opened Mar 18, 2026 by outlook84

What version of Codex CLI is running?

codex-cli 0.115.0

What subscription do you have?

Team

Which model were you using?

gpt-5.4-medium,gpt-5.4-xhigh

What platform is your computer?

Linux 6.6.87.2-microsoft-standard-WSL2 x86_64 unknown

What terminal emulator and version are you using (if applicable)?

Google Antigravity

What issue are you seeing?

### Summary

I found what looks like a Codex sandbox issue.

Inside the Codex sandbox, Python async code that relies on cross-thread event loop wakeups
can hang or time out. This affects:

  • asyncio.call_soon_threadsafe(...)
  • asyncio.to_thread(...)
  • loop.run_in_executor(None, ...)
  • anyio.to_thread.run_sync(...)
  • anyio.Path(...).exists() and similar helpers that offload sync work to threads

Outside the sandbox, the same commands complete successfully.

### Environment

  • Codex CLI sandbox
  • Reproduced on March 18, 2026
  • Project Python: .venv/bin/python3
  • Python version: 3.13
  • pytest: 9.0.2
  • pytest-asyncio: 1.3.0
  • anyio: 4.12.1

What steps can reproduce the bug?

### Reproduction

I used this command in a repo only as a probe harness:

``bash
.venv/bin/python3 scripts/probe_async_executor.py --timeout 2
``
probe_async_executor.py

Inside the sandbox, the probe showed this pattern:

  • asyncio.to_thread(...): timeout
  • loop.run_in_executor(None, ...): timeout
  • anyio.to_thread.run_sync(...): timeout
  • anyio.Path(...).exists(): timeout
  • custom ThreadPoolExecutor(...) cases: success

Outside the sandbox, the exact same command completed successfully and all probe cases
passed.

### Minimal repro

This hangs inside the sandbox:
```
import asyncio
import threading

def cb(loop):
print("thread:call_soon_threadsafe", flush=True)
loop.call_soon_threadsafe(lambda: (print("loop:callback", flush=True), loop.stop()))

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
threading.Timer(0.5, cb, args=(loop,)).start()
loop.run_forever()
print("done", flush=True)
loop.close()

Expected output:

thread:call_soon_threadsafe
loop:callback
done
```

Actual behavior inside the sandbox:

  • only thread:call_soon_threadsafe prints
  • the event loop never wakes up
  • the process hangs until killed / times out

### Root cause evidence

With PYTHONASYNCIODEBUG=1, asyncio logs the real failure:

DEBUG:asyncio:Fail to write a null byte into the self-pipe socket
Traceback (most recent call last):
File "/usr/lib/python3.13/asyncio/selector_events.py", line 152, in _write_to_self
csock.send(b'\0')
PermissionError: [Errno 1] Operation not permitted

This appears to break the Unix selector event loop wakeup mechanism used by
call_soon_threadsafe().

Relevant stdlib path:

  • asyncio/selector_events.py, _write_to_self() uses csock.send(b'\0')
  • asyncio/base_events.py, shutdown_default_executor() depends on call_soon_threadsafe(...)

That explains why some asyncio.to_thread(...) work appears to finish but asyncio.run(...)
still hangs during shutdown of the default executor.

### Extra confirmation

I also verified that this is not specific to the repo or virtualenv startup hooks:

  • reproduces with plain python3
  • reproduces with python3 -S
  • does not require importing any project code
  • disappears when rerun outside the Codex sandbox

I also tested lower-level primitives:

  • cross-thread socket.socketpair().send(...) fails with PermissionError: [Errno 1] Operation

not permitted

  • cross-thread os.pipe() write works

So this looks specifically related to the sandbox blocking the socketpair-based wakeup path
used by asyncio on Unix.

What is the expected behavior?

### Expected behavior

Codex sandboxed Python processes should be able to use standard-library asyncio thread
wakeups correctly, or the sandbox should detect and clearly report that this runtime pattern
is unsupported.

### Actual behavior

The sandbox causes false async failures that look like application bugs:

  • hangs in asyncio.to_thread(...)
  • hangs in anyio.to_thread.run_sync(...)
  • hangs during shutdown_default_executor()
  • misleading timeouts in pytest and repo diagnostics

Additional information

_No response_

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗