fix(code-runtime-python): bind the original std streams' flush methods, not the stream objects

The settlement drain iterated the bound stream OBJECTS, which are not
callable — every _flush() raised TypeError and was swallowed by the loop's
except, so the drain never ran and only the -u flag carried the behavior.
Bind sys.__stdout__.flush/sys.__stderr__.flush (bound methods, capturing the
stream at binding time, immune to a later sys.__stdout__ rebind; None-guarded).
Verified by removing -u temporarily: the sys.__stdout__ regression test still
passes, so the drain is a genuine backstop, not a documented-but-dead layer.
This commit is contained in:
Chinesezjc
2026-08-31 14:47:18 +08:00
committed by Tianyi Cui
parent 43a0879ad1
commit 1efb0094c8
@@ -958,8 +958,14 @@ async def _run(channel: ProtocolChannel) -> None:
# Binding the names here (before the program) makes them immune to a
# `sys.__stdout__ = boom` rebind in model code; `None` under `-S`-style
# redirects is guarded at flush time.
_stdout_orig = sys.__stdout__
_stderr_orig = sys.__stderr__
# Bind the FLUSH METHODS, not the stream objects: the settlement flush
# loop iterates callables, and a bare TextIOWrapper object is not callable —
# invoking it would raise TypeError and be swallowed by the loop's except,
# silently disabling the drain. A bound method captures its stream at
# binding time, so a later `sys.__stdout__ = boom` rebind cannot redirect
# it; `None` (stream absent) is guarded at flush time.
_stdout_orig = sys.__stdout__.flush if sys.__stdout__ is not None else None
_stderr_orig = sys.__stderr__.flush if sys.__stderr__ is not None else None
# 6. Compile the program as the body of an async function, matching the
# seam contract (`CodeRunRequest.program` is an async-function body: top-level