Skip to content
Draft
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
58 changes: 55 additions & 3 deletions examples/sandbox_14_pty_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
load_dotenv()

EXPECTED_OUTPUT = "PTY_OK"
READY_OUTPUT = "PTY_READY"
DONE_OUTPUT = "PTY_DONE"
PROMPT_MARKER = "$ "


Expand All @@ -28,20 +30,44 @@ async def collect_output_until(
marker: str,
*,
timeout: float = 30.0,
require_full_line: bool = False,
) -> bytes:
"""Read PTY output until the expected marker appears."""

async def _collect() -> bytes:
output = b""
marker_bytes = marker.encode()
async for data in session.stream:
output += data
if marker.encode() in output:
if require_full_line:
if any(line.rstrip(b"\r") == marker_bytes for line in output.splitlines()):
return output
elif marker_bytes in output:
return output
return output

return await asyncio.wait_for(_collect(), timeout=timeout)


async def run_and_collect(
session: AsyncPTYSession,
command: str,
marker: str,
*,
timeout: float = 30.0,
require_full_line: bool = False,
) -> bytes:
"""Send a command to the PTY and collect output until a marker appears."""

await session.stream.send(command.encode())
return await collect_output_until(
session,
marker,
timeout=timeout,
require_full_line=require_full_line,
)


async def main() -> int:
print("=" * 60)
print("Low-level AsyncPTYSession Example")
Expand Down Expand Up @@ -83,11 +109,34 @@ async def main() -> int:
print(initial_output_text.rstrip())
print("-" * 60)

print("Verifying PTY input/output round-trip...")
try:
ready_output = await run_and_collect(
session,
f"printf '{READY_OUTPUT}\\n'\n",

Copilot AI Apr 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collect_output_until(session, READY_OUTPUT) can return as soon as the shell echoes the typed command, because the input line includes the literal text PTY_READY inside quotes. That means this “round-trip” check may succeed without waiting for the printf output to be produced/executed, defeating the goal of ensuring PTY readiness. Consider matching the marker as a full output line (e.g., require newline boundaries around it) or emitting a sentinel that does not appear verbatim in the echoed command (e.g., decode/construct the marker at runtime) before searching for it.

Suggested change
f"printf '{READY_OUTPUT}\\n'\n",
"printf '%s\\n' 'PTY_''READY'\n",

Copilot uses AI. Check for mistakes.
READY_OUTPUT,
require_full_line=True,
)
except asyncio.TimeoutError:
print("Timed out waiting for PTY round-trip confirmation.")
return 1

ready_output_text = ready_output.decode("utf-8", errors="replace")
print()
print("Received PTY round-trip output:")
print("-" * 60)
print(ready_output_text.rstrip())
print("-" * 60)

print("Writing a simple command through the PTY stream...")
await session.stream.send(f"printf '{EXPECTED_OUTPUT}\\n'; pwd; exit\n".encode())

try:
output = await collect_output_until(session, EXPECTED_OUTPUT)
output = await run_and_collect(
session,
f"printf '{EXPECTED_OUTPUT}\\n'; pwd; printf '{DONE_OUTPUT}\\n'; exit\n",
DONE_OUTPUT,
require_full_line=True,
)
Comment on lines +134 to +139

Copilot AI Apr 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting for DONE_OUTPUT via substring search has the same false-positive risk as the READY check: the PTY will typically echo the command line, which contains PTY_DONE inside quotes, so output collection may stop before the command finishes running (and before exit completes). To make this a reliable completion sentinel, detect the marker as an actual output line (newline-delimited) or print a marker that isn’t present in the echoed input (e.g., construct it via decoding) and then wait for that.

Copilot uses AI. Check for mistakes.
except asyncio.TimeoutError:
print("Timed out waiting for PTY output.")
return 1
Expand All @@ -102,6 +151,9 @@ async def main() -> int:
if EXPECTED_OUTPUT not in output_text:
print("Expected PTY output marker was not observed.")
return 1
if DONE_OUTPUT not in output_text:
print("PTY command completion marker was not observed.")
return 1

print()
print("Low-level PTY session flow completed successfully.")
Expand Down
Loading