Async SQLite hang in Codex runtime (aiosqlite + SQLAlchemy)
Summary
In the Codex execution environment, async SQLite connections using aiosqlite hang when used through SQLAlchemy async (sqlite+aiosqlite). The same code runs fine in a normal interactive terminal on the same machine (Python 3.13.9, uv, same dependencies).
This appears to be a runtime/environment interaction (Codex command runner) rather than a project bug. It only reproduces when network access is disabled by the sandbox and does not reproduce when network access is enabled.
Environment
- Codex execution environment (non-interactive command runner)
- Codex CLI: 0.91.0
- OS: Arch Linux (kernel 6.18.3-arch1-1, x86_64)
- Python: 3.13.9 (uv managed)
- aiosqlite: 0.21.0
- SQLAlchemy: 2.0.44
- greenlet: 3.2.4
Repro (hangs in Codex runtime)
uv run --extra test python -u - <<'PY'
import asyncio
from sqlalchemy.ext.asyncio import create_async_engine
async def main():
engine = create_async_engine("sqlite+aiosqlite:///:memory:", future=True)
print("engine created")
async with engine.connect():
print("connected")
await engine.dispose()
asyncio.run(main())
PY
Expected: prints "connected". Actual in Codex runtime: prints "engine created" and hangs.
Control (works in normal terminal)
This script works when run in a standard terminal (outside Codex runner):
uv run --extra test python -u - <<'PY'
import asyncio
import aiosqlite
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine
async def main():
print("aiosqlite connect start")
async with aiosqlite.connect(":memory:") as db:
await db.execute("select 1")
print("aiosqlite connect ok")
print("sqlalchemy async connect start")
engine = create_async_engine("sqlite+aiosqlite:///:memory:", future=True)
async with engine.connect() as conn:
await conn.execute(text("select 1"))
await engine.dispose()
print("sqlalchemy async connect ok")
asyncio.run(main())
PY
Instrumentation findings
Instrumentation inside aiosqlite.core.Connection shows:
- The background thread executes the connector and calls
future.set_result(...)successfully. - The Future is marked done and its done-callback fires.
- The awaiting coroutine in
_connect()never resumes and eventually times out.
Key logs (abbreviated):
thread executing connector ...
thread function result <sqlite3.Connection ...>
thread scheduled set_result
future done callback True cancelled False
await failed TimeoutError
The hang occurs inside SQLAlchemy's async path:
AsyncConnection.start()->greenlet_spawn(self.sync_engine.connect)greenlet_spawnawaits the aiosqlite Connection awaitable and never resumes.
This suggests a subtle interaction between the Codex runner event loop, greenlet-based SQLAlchemy async bridge, and aiosqlite's thread handoff.
Additional datapoint (sync works)
Synchronous SQLite works in the Codex environment:
engine created
connected
executed
done
This further points to the async path specifically. One hypothesis is that aiosqlite's background-thread handoff (via loop.call_soon_threadsafe) interacts poorly with the Codex sandbox/event-loop setup. I don't yet have a minimal repro that fails in all Codex contexts, so treat this as a working theory rather than a confirmed root cause.
Network access toggles the hang
With network access enabled in this Codex environment, the hang does not reproduce.
With network access revoked (default sandbox restrictions), the hang reproduces.
Last verified
2026-01-26
Notes
- Bumping
aiosqlite(0.22.1) andsqlalchemy(2.0.46) did not fix it in the Codex runtime. - The same dependencies work in a normal terminal (non-Codex runner).
Impact
Async SQLite usage (via sqlite+aiosqlite) hangs under the Codex runner when sandbox network access is disabled, preventing test execution.
4 Comments
If you're running in a sandbox with a "no network" policy, a tool will receive errors when it attempts to access the network. It sounds like the tool or script isn't properly handling network errors.
You may want to report the issue to the maintainers of this tool / script.
Thanks for the quick reply! I've dug into the Codex sandbox source and I believe I've found the root cause.
The issue isn't that aiosqlite is making network calls - SQLite is purely local. The problem is that Python's asyncio call_soon_threadsafe() uses the event loop's self-pipe/wakeup mechanism to signal the main thread, and this is blocked by the seccomp filter.
Looking at codex-rs/linux-sandbox/src/landlock.rs:
deny_syscall(libc::SYS_sendto); // line 118
sendto is blocked unconditionally. However, Python's call_soon_threadsafe uses os.write() on the self-pipe (or sendto on AF_UNIX socket pairs on some platforms) to wake the event loop from another thread.
The flow:
aiosqlite's background thread completes the SQLite operation
It calls loop.call_soon_threadsafe(future.set_result, result)
call_soon_threadsafe tries to wake the event loop via the self-pipe
The seccomp filter blocks the wakeup syscall with EPERM
The main thread's event loop never wakes up → hang
This explains why enabling network access fixes it - the seccomp filter isn't installed when has_full_network_access() is true.
Suggested fix: Allow sendto for AF_UNIX sockets (similar to how socket/socketpair are handled), rather than blocking it unconditionally. This would preserve the no-network guarantee while allowing local IPC and event loop wakeups.
I can open pull request if my assumption is correct
Thanks for the analysis. Before moving to a PR, could you confirm your hypothesis by prototyping the proposed fix and confirming that it addresses the issue?
I prototyped the fix and validated it locally.
Change: in
codex-rs/linux-sandbox/src/landlock.rs, allowsendtoonly whendest_addr == NULLandaddrlen == 0(i.e. send() / self‑pipe wakeup). connect,bind, listen, etc. remain blocked.
Results:
I can open a PR with the change (and a small regression test if you want).