ZhuLinsen/daily_stock_analysis · error · CodexAppServerError
resource_cleanup_failed
resource_cleanup_failed
Error message
Codex App Server process group could not be reclaimed
What it means
Raised by the transport's stop/cleanup path when, after SIGTERM, then SIGKILL of the child and a 2-second bounded wait (kill_deadline), the process still has poll() is None or its process group remains alive (_process_group_alive checks the pgid). The App Server is spawned in its own process group precisely so it can be reclaimed wholesale.
Source
Thrown at src/agent/codex_app_server_transport.py:1048
if process.poll() is None or group_alive:
try:
os.killpg(process_group_id, signal.SIGKILL)
except ProcessLookupError:
pass
except PermissionError:
if process.poll() is None:
process.kill()
kill_deadline = time.monotonic() + 2
if process.poll() is None:
try:
process.wait(timeout=max(0.0, kill_deadline - time.monotonic()))
except subprocess.TimeoutExpired:
pass
while _process_group_alive(process_group_id) and time.monotonic() < kill_deadline:
time.sleep(0.02)
if process.poll() is None or _process_group_alive(process_group_id):
raise CodexAppServerError(
"resource_cleanup_failed",
"Codex App Server process group could not be reclaimed",
)
def resolve_command(executable: str = "codex") -> list[str]:
"""Resolve the fixed App Server argv and least-privilege overrides."""
if is_native_windows():
raise CodexAppServerError(
"capability_unsupported",
"Codex App Server Agent is not supported on native Windows in this phase",
)
resolved = shutil.which(executable)
if resolved is None:
raise CodexAppServerError("command_not_found", "Codex executable was not found")
command = [resolved, "app-server", "--stdio"]
for override in _BASE_CONFIG_OVERRIDES:
command.extend(["-c", override])View on GitHub (pinned to 5159bd72e8)
Solutions
- Inspect which PIDs remain: ps -o pid,ppid,pgid,stat -g <pgid> to find survivors (look for 'D' state or stray grandchildren)
- If an MCP child escaped the group, kill it explicitly by PID or file the leak as a codex bug
- Increase the kill_deadline (currently hardcoded 2s) if the machine is slow, or retry the group kill once
- Call stop() from the same thread that owns the Popen, or ensure waitpid is not blocked by threading constraints
Defensive patterns
Strategy: retry
Try / catch
try:
transport.stop()
except CodexAppServerError as exc:
if exc.code == "resource_cleanup_failed":
os.killpg(pgid, signal.SIGKILL) # second, manual sweep
time.sleep(1)
# log survivors; leak is bounded and known
logger.warning("app-server pgid %d needed manual sweep", pgid) Prevention
- Always stop() through the transport's own cleanup path
- Log surviving PIDs on cleanup failure
- Keep the 2s kill budget generous on loaded machines
When it happens
Trigger: SIGKILL was delivered but the child is stuck in uninterruptible sleep (D state) on NFS/device IO, or the process group still contains live grandchildren (MCP servers spawned by codex) that ignore/escape the group kill; also possible zombie state never reaped because wait() was called from a different thread than the Popen owner.
Common situations: Running under a container/node with IO stalls; codex spawning MCP servers outside the killed pgid (setsid'd helpers); zombie accumulation when Popen.wait() races; heavy load making the 2s deadline too tight.
Related errors
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/ece29706b1e18842.
Report an issue: GitHub.