Skip to content

The engine refuses its own codex app-server socket at mode 0755: codex binds under the inherited umask and chmods afterwards, and the engine verifies at first sight #136

Closed

Description

@Simon-AI-coding

Part of #67 — found by the v1.0 exit run #135 (20260828T074856Z, codex lane refused at engine start; claude lane 9/9 green).

What happened

The codex-lane engine exited 2 before binding its control socket:

AppServerError: /tmp/gpt-voicecoding-501/codex-app-server.sock is reachable by other accounts (mode 0755); refusing to use it
  adapters/codex_app_server/process.py:260 attach → :217 verify_private_socket

That socket is the engine's own spawned codex app-server (process.py:_spawn, line ~457), not the shared daemon.

Root cause — measured, not inferred (advisor, 2026-08-28)

codex 0.150.1 creates its listen socket under the inherited umask (0755 with the default 022) and chmods it to 0600 afterwards. Five spawns polled at 1 ms: four first seen at 0600, one first seen at 0755 and 0600 a moment later; steady state is always 0600. The engine's _await_socket (process.py:~484) returns the instant is_socket() is true and attach verifies immediately, so under load it lands inside that window. _spawn passes no umask, so the child inherits whatever the engine has — the same bind→chmod window #116 closed for the engine's own listeners (private_socket.py:9-26: the mode comes from the umask at creation time), left open for the one listener the engine does not bind itself.

Fix

subprocess.Popen(..., umask=0o077) in _spawn — Python ≥ 3.9 sets it in the child between fork and exec. The socket is then born 0600 and the verify guard stays exactly as it is; no retry loop, no sleep, no relaxation of verify_private_socket. Derive the value from PRIVATE_SOCKET_MODE (0o777 & ~PRIVATE_SOCKET_MODE), the way private_socket.py:82 does — no second constant.

Test: spawn a tiny Python child through the same _spawn path (or a fake executable the tests already use) that binds a socket and reports its mode at creation; assert 0600. Also a test that the codex adapter's spawn passes the umask argument — the fake runner fixture in tests/conftest.py applies if a real process is not wanted.

Legacy

legacy@1d32845:bridge/codex.py:290-300 spawns the same app-server with no umask and had no privacy guard at all — the guard is v1's (#47/#116). New, not ported.

Also seen on the run, not this ticket's

The codex discovery degradation line "the Codex CLI is '0.150.1' and the running app-server is '0.149.1' — a Session started by this CLI will not join that daemon" appeared, as it did 93 times on the green run of record 20260827T100454Z (CLI 0.150.0) where #105 measured such a Session does join. The wording asserts what a measurement contradicted; low priority, recorded on #67.

Routing

Workflow: tdd
Executor: codex
Model: gpt-5.6-sol
Effort: high
Review: claude claude-opus-5 medium
Reasons: New runtime behaviour a mode-at-creation test pins (tdd); adapter-local, nothing downstream couples to it (non-core); approach fixed on the ticket, one line (routine); model and effort per Simon's 2026-08-28 override; serial chain head.

Done when

ruff/pytest green; PR with provenance; ticket open for the advisor to land. Then #135 re-runs in full (a code change after a run means a re-run).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingwayfinder:taskWayfinder task ticket

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions