Python SDK: CodexClient.close() leaves the app-server's stdout/stderr pipes open (and never waits after kill)

Aperta Adatta ai principianti
#54,908 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@akhil9tiet ci sta già lavorando.

Dal 17/10/2026.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
82/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
python
Ambito
backend

Direzione di ricerca

Inizia da CodexClient.close() in sdk/python/src/openai_codex/client.py, quindi esamina start() e il percorso di chiusura sincrono utilizzato dai client asincroni. Usa il processo sostitutivo launch_args_override fornito con python -X dev ed esegui i test SDK pertinenti Treatendo le ResourceWarning come errori. Fatto significa che il processo è stato terminato e raccolto, i thread del reader si sono fermati, tutte e tre le pipe sono chiuse e non rimane alcuna ResourceWarning.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

app-server bug
What issue are you seeing?

In the Python SDK, CodexClient.close() (sdk/python/src/openai_codex/client.py, lines 283–302 on main) never closes the app-server child process's stdout and stderr pipes. It closes stdin, terminates the process and joins the reader threads, but the two subprocess.PIPE file objects that start() opened (lines 268–278) stay open. They are only finalized whenever the Popen object is garbage-collected, and CPython reports each one:

ResourceWarning: unclosed file <_io.TextIOWrapper name=5 encoding='utf-8'>
ResourceWarning: unclosed file <_io.TextIOWrapper name=4 encoding='utf-8'>

AsyncCodex.close() / AsyncCodexClient.close() delegate to the same method, so every client the SDK creates leaves two handles to the garbage collector. A test suite that runs with -W error::ResourceWarning, or filterwarnings = error, fails on any test that closes a client. A long-running host that opens and closes many clients holds those handles until collection. On interpreters without prompt refcounting (PyPy), or when a reference cycle keeps the Popen alive, they can pile up.

A second, related gap, found by reading the code but not reproduced: if proc.wait(timeout=2) raises (for example subprocess.TimeoutExpired because the app-server doesn't exit within 2 s of SIGTERM on POSIX), close() calls proc.kill() but never waits for the process afterwards. On POSIX that leaves a zombie until the Popen is collected, and Popen.__del__ then warns ResourceWarning: subprocess <pid> is still running. The bare except Exception: also hides any other failure from terminate() / wait().

What steps can reproduce the bug?

This needs no Codex installation or sign-in. launch_args_override starts a stand-in process that just waits on stdin, which is enough because close() alone shows the problem.

"""CodexClient.close() leaves the app-server's stdout and stderr pipes open.

Needs no Codex install or sign-in: the launch override starts a stand-in process that waits on stdin.
Run with:  python -X dev repro_close_leaks_pipes.py
"""

import gc
import sys
import warnings

import openai_codex
from openai_codex.client import CodexClient, CodexConfig

warnings.simplefilter("always", ResourceWarning)

print(f"openai-codex {openai_codex.__version__ if hasattr(openai_codex, '__version__') else '?'}, Python {sys.version.split()[0]}, {sys.platform}")

client = CodexClient(CodexConfig(launch_args_override=(sys.executable, "-c", "import sys; sys.stdin.read()")))
client.start()
proc = client._proc  # kept only to show the pipes' state after close()
client.close()

print(f"after close(): returncode={proc.returncode}, stdin.closed={proc.stdin.closed}, stdout.closed={proc.stdout.closed}, stderr.closed={proc.stderr.closed}")

del proc, client
gc.collect()
print("done")

Output on Windows 11 with Python 3.14.2, identical for openai-codex 0.162.1 and 0.163.0 (latest on PyPI at the time of writing), shown here for 0.163.0 (stderr warnings first):

repro_close_leaks_pipes.py:25: ResourceWarning: unclosed file <_io.TextIOWrapper name=5 encoding='utf-8'>
  del proc, client
ResourceWarning: Enable tracemalloc to get the object allocation traceback
repro_close_leaks_pipes.py:25: ResourceWarning: unclosed file <_io.TextIOWrapper name=4 encoding='utf-8'>
  del proc, client
ResourceWarning: Enable tracemalloc to get the object allocation traceback
openai-codex 0.163.0, Python 3.14.2, win32
after close(): returncode=1, stdin.closed=True, stdout.closed=False, stderr.closed=False
done

AsyncCodex and AsyncCodexClient close through this same method. We first saw the warnings in an acceptance suite that runs the production async client against a mediated app-server process.

What is the expected behavior?

After close() returns:

  • The child process has exited and been waited for (reaped), including on the kill() path.
  • The reader threads have finished.
  • stdin, stdout and stderr are all closed.
  • No ResourceWarning is emitted when the client is garbage-collected.

A possible fix in CodexClient.close():

        if proc.stdin:
            proc.stdin.close()
        try:
            proc.terminate()
            proc.wait(timeout=2)
        except subprocess.TimeoutExpired:
            proc.kill()
            proc.wait()

        # The process has exited, so both reader threads reach EOF; join them before closing the pipes they read.
        if self._stderr_thread and self._stderr_thread.is_alive():
            self._stderr_thread.join(timeout=0.5)
        if self._reader_thread and self._reader_thread.is_alive():
            self._reader_thread.join(timeout=0.5)
        for stream in (proc.stdout, proc.stderr):
            if stream is not None:
                stream.close()

Closing the pipes after the threads are joined avoids closing a file object another thread is still iterating. Catching subprocess.TimeoutExpired instead of Exception keeps unexpected failures visible. Alternatively, with proc: around the teardown closes all three pipes and waits.

Additional information
  • Package: openai-codex 0.162.1 and 0.163.0 (sdk/python in this repo). The code is unchanged on main.
  • Python 3.14.2 on Windows 11. The pipe leak is platform-independent. The missing wait after kill() matters mainly on POSIX.
  • This is separate from #26984. That issue covers the CLI leaking pipes and orphan processes for MCP stdio servers it starts; this one is the Python SDK's own handles to the app-server process.
  • Our workaround would be to close proc.stdout / proc.stderr ourselves after close(). That means reaching into CodexClient._proc / AsyncCodexClient._sync, which are private, so we'd rather see it fixed in the SDK.
Lingua principale
Rust
Stelle
133k
Fork
21.1k
Merge medio
1m
PR unite (30g)
997

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di openai/codex

Tutte le issue di openai/codex

Issue simili

Altre issue su Rust

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.