{"record":{"id":"ece29706b1e18842","repo":"ZhuLinsen/daily_stock_analysis","slug":"resource-cleanup-failed","errorCode":"resource_cleanup_failed","errorMessage":"Codex App Server process group could not be reclaimed","messagePattern":"Codex App Server process group could not be reclaimed","errorType":"error_code","errorClass":"CodexAppServerError","httpStatus":null,"severity":"error","filePath":"src/agent/codex_app_server_transport.py","lineNumber":1048,"sourceCode":"            if process.poll() is None or group_alive:\n                try:\n                    os.killpg(process_group_id, signal.SIGKILL)\n                except ProcessLookupError:\n                    pass\n                except PermissionError:\n                    if process.poll() is None:\n                        process.kill()\n                kill_deadline = time.monotonic() + 2\n                if process.poll() is None:\n                    try:\n                        process.wait(timeout=max(0.0, kill_deadline - time.monotonic()))\n                    except subprocess.TimeoutExpired:\n                        pass\n                while _process_group_alive(process_group_id) and time.monotonic() < kill_deadline:\n                    time.sleep(0.02)\n\n            if process.poll() is None or _process_group_alive(process_group_id):\n                raise CodexAppServerError(\n                    \"resource_cleanup_failed\",\n                    \"Codex App Server process group could not be reclaimed\",\n                )\n\n\ndef resolve_command(executable: str = \"codex\") -> list[str]:\n    \"\"\"Resolve the fixed App Server argv and least-privilege overrides.\"\"\"\n    if is_native_windows():\n        raise CodexAppServerError(\n            \"capability_unsupported\",\n            \"Codex App Server Agent is not supported on native Windows in this phase\",\n        )\n    resolved = shutil.which(executable)\n    if resolved is None:\n        raise CodexAppServerError(\"command_not_found\", \"Codex executable was not found\")\n    command = [resolved, \"app-server\", \"--stdio\"]\n    for override in _BASE_CONFIG_OVERRIDES:\n        command.extend([\"-c\", override])","sourceCodeStart":1030,"sourceCodeEnd":1066,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/agent/codex_app_server_transport.py#L1030-L1066","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    transport.stop()\nexcept CodexAppServerError as exc:\n    if exc.code == \"resource_cleanup_failed\":\n        os.killpg(pgid, signal.SIGKILL)  # second, manual sweep\n        time.sleep(1)\n        # log survivors; leak is bounded and known\n        logger.warning(\"app-server pgid %d needed manual sweep\", pgid)","preventionTips":["Always stop() through the transport's own cleanup path","Log surviving PIDs on cleanup failure","Keep the 2s kill budget generous on loaded machines"],"tags":["codex","app-server","process-cleanup","process-group","sigkill"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}