Skip to content

[Bug][Linux] native-main 503 when systemctl exists but the user bus is unreachable (#1612 regression gap) #2114

Description

@lidge-jun

Client or integration

Codex CLI

Area

Service lifecycle

Summary

OpenCodex 2.26.0 blocks every native OpenAI request on a systemd-less Linux container when systemctl is present on PATH but the user bus is unreachable. The proxy stays live, /healthz returns 200, and ocx ready reports {"ready":true,"status":"ready"}, but every native request fails locally with HTTP 503 before reaching upstream:

{"error":{"message":"OpenCodex local native-main profile maintenance is active; retry this request","type":"server_error","code":"server_is_overloaded"}}

This is the same admission fence as #1612, but that fix does not cover this environment. #1612 was closed by treating a systemctl spawn failure as absent. In this container systemctl spawns fine — it exits non-zero because there is no user bus:

$ command -v systemctl
/usr/bin/systemctl

$ systemctl --user show opencodex-proxy -p LoadState -p ActiveState -p FragmentPath -p NeedDaemonReload
Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined
EXIT=1

inspectSystemd() only maps spawnFailed to absent; a non-zero exit still returns unknown, which re-triggers the native-main block. The /.dockerenv marker is present and PID 1 is tini, so this is exactly the externally-supervised foreground shape src/service.ts recommends (Service: unsupported in Docker, "run ocx start directly").

Reproduction

  1. Debian 13 container (PID 1 = tini, /.dockerenv present) with systemd installed but no user bus: /run/user/$UID empty, XDG_RUNTIME_DIR and DBUS_SESSION_BUS_ADDRESS unset.
  2. npm install -g @openai/codex@0.148.0 @bitkyc08/opencodex@2.26.0
  3. codex login (ChatGPT), confirm native codex exec works.
  4. ocx start, then ocx sync.
  5. Send a native request:
$ curl -s -X POST http://127.0.0.1:10100/v1/responses \
    -H 'Content-Type: application/json' \
    -d '{"model":"gpt-5.5","input":"hi","stream":false}'
{"error":{"message":"OpenCodex local native-main profile maintenance is active; retry this request",...}}

codex exec surfaces this as a misleading message, which is what a user actually sees:

ERROR: Selected model is at capacity. Please try a different model.

ocx restart does not clear it; ocx ready still reports ready:true while every request 503s.

Control flow

systemd installed, no user bus (Docker/tini)
-> inspectSystemd() runs `systemctl --user show` -> spawn SUCCEEDS, exit 1 (no bus)
-> service-manager-probe.ts: !spawnFailed, status !== 0 -> unknown("systemctl show exited 1: ...")
-> ownership-preflight.ts: manager.kind === "unknown" -> ownership: "unknown"
-> server/index.ts: blockNativeMainStartupForUnownedServiceHome("ownership-unknown")
-> native-profile-startup.ts: isNativeMainTrafficBlocked() === true
-> auth-context.ts: throw CodexMainProfileDrainingError -> 503

The #1612 fix (if (shown.spawnFailed) return { kind: "absent" }) sits one line above the branch that fires here.

Verified workaround

Removing systemctl from the proxy's PATH makes the probe hit the spawnFailed path fixed in #1612, and native traffic recovers immediately:

$ mkdir -p ~/ocx-nosysd && for f in /usr/bin/*; do b=$(basename $f); [ "$b" = systemctl ] && continue; ln -sf $f ~/ocx-nosysd/$b; done
$ ocx stop
$ setsid env PATH=$HOME/ocx-nosysd:/usr/local/bin ocx start &

$ curl -s -X POST http://127.0.0.1:10100/v1/responses -H 'Content-Type: application/json' -d '{"model":"gpt-5.5","input":"hi"}'
{"detail":"Input must be a list"}          # request reached the handler; no 503

$ codex exec "Reply with exactly: PROXY_OK"
PROXY_OK                                    # tokens used 3,922

Same binary, same config, same login — the only variable is whether systemctl is reachable on PATH. That isolates the probe result as the sole cause.

Setting XDG_RUNTIME_DIR=/run/user/1000 does not help: the directory exists but has no bus socket, so systemctl still exits non-zero ("Failed to connect to user scope bus via local transport: No such file or directory").

Suggested fix

Treat "the question never reached a user bus" the same as an unaskable manager, while keeping fail-closed behavior for real evidence.

  1. In inspectSystemd(), classify a no-bus failure as absent rather than unknown. Prefer matching the bus-connection failure specifically (stderr Failed to connect to ... bus), not any non-zero exit.
  2. Guard it with the container/foreground signal already used elsewhere (/.dockerenv, PID 1 not systemd), so a real desktop Linux box with a transient bus problem still returns unknown.
  3. Keep every other path fail-closed: a present unit, NeedDaemonReload=yes, malformed state, conflicting mirrors, or foreign-home evidence must still yield unknown/foreign.

Regression coverage worth adding:

  • Docker + systemctl present + bus unreachable (exit 1) -> native-main admits traffic.
  • Docker + systemctl present + unit actually installed for a foreign home -> still blocked.
  • Non-Docker Linux + bus unreachable -> still unknown (no widening on real hosts).

Two secondary issues

1. The user-facing error is wrong. A local admission fence is reported to the CLI as ERROR: Selected model is at capacity. Please try a different model. That sends users to change models or wait for quota, when the cause is local and the fix is ocx restore or the PATH workaround. The 503 body is accurate; the CLI-visible mapping is not.

2. ocx ready reports ready while every request 503s. {"ready":true,"status":"ready"} with a 100% failure rate makes readiness unusable as a health signal in exactly the automated/container context where it matters most. Readiness could reflect isNativeMainTrafficBlocked() when native-main is the only configured route.

Also worth noting: ocx status correctly says Service: unsupported in Docker and ocx doctor warns AT RISK after restart (no viable background service; run 'ocx restore'). The product already knows it is in an unsupported-service environment at the moment it blocks traffic for not being able to prove service ownership.

Environment

  • OpenCodex 2.26.0 (npm global), Codex CLI 0.148.0
  • Debian GNU/Linux 13 (trixie), kernel 6.12.94, x86_64
  • Container: /.dockerenv present, PID 1 = tini, /run/user/1000 empty
  • systemd installed (/usr/bin/systemctl), user bus unreachable
  • Bundled Bun 1.3.14 runtime, port 10100
  • Auth: codex login (ChatGPT), native codex exec verified working before ocx sync

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansbugSomething isn't workinginstallInstallation or packagingserviceService lifecycle (WinSW/launchd/scheduler)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions