Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions agent/codex_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -922,9 +922,32 @@ def run_codex_create_stream_fallback(agent, api_kwargs: dict, client: Any = None
return run_codex_stream(agent, api_kwargs, client=client)


def close_codex_session(agent) -> None:
"""Close the Codex app-server session if it exists.

Ephemeral agents (e.g., cron jobs) need explicit cleanup to avoid
leaking the ``codex app-server`` subprocess. The session is closed on
the turn-crash, ``should_retire``, and compression-drop paths, but
cron's ``finally`` block tears down the agent via ``agent.close()``,
which does not include this cleanup.

This helper is safe to call multiple times — ``CodexAppServerSession.close()``
is idempotent.
"""
session = getattr(agent, "_codex_session", None)
if session is None:
return
try:
session.close()
except Exception:
logger.debug("codex app-server session close failed", exc_info=True)
agent._codex_session = None


__all__ = [
"run_codex_app_server_turn",
"run_codex_stream",
"run_codex_create_stream_fallback",
"_consume_codex_event_stream",
"close_codex_session",
]
8 changes: 8 additions & 0 deletions cron/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3336,6 +3336,14 @@ def _teardown_cron_agent(agent, job_id: str) -> None:
agent.close()
except (Exception, KeyboardInterrupt) as e:
logger.debug("Job '%s': failed to close agent resources: %s", job_id, e)
# Close the Codex app-server session if it exists. Ephemeral agents
# (cron jobs) leak the subprocess without this cleanup (#62101).
try:
from agent.codex_runtime import close_codex_session
if agent is not None:
close_codex_session(agent)
except (Exception, KeyboardInterrupt) as e:
logger.debug("Job '%s': failed to close codex session: %s", job_id, e)
# Each cron run spins up a short-lived worker thread whose event loop
# dies as soon as the ``ThreadPoolExecutor`` shuts down. Any async
# httpx clients cached under that loop are now unusable — reap them
Expand Down
Loading