Skip to content

[Bug]: reattached stdout stream stalls after auto-resume (regression in python-sdk 2.29.1) #1587

Description

@nseniak

Summary

After a sandbox auto-pauses and resumes, reattaching to an existing
long-lived background process via commands.connect(pid) produces a
stdout stream that intermittently delivers its first response and then
goes silent — the on_stdout callback stops firing for subsequent
responses even though the process is still running and still writing.

This is a regression introduced in python-sdk 2.29.1 and present in
every release since (through 2.34.0). The likely change is cb061d2
("Fix several command and PTY streaming issues"), which reworked
incremental stdout/stderr decoding and stream-connection teardown.

Bisect (strong evidence)

Identical workload, same account/templates; only the SDK version changed.
The workload is a real stdio server (@modelcontextprotocol/server-everything
over npx) driven through repeated pause → resume → commands.connect(pid)
reattach cycles. "Stall rate" = fraction of reattach cycles where the
reconnected stdout stream failed to deliver a complete response, over 6
cycles:

Version Stall rate
2.20.2 1/6
2.21.0 1/6
2.27.0 1/6
2.29.0 1/6 ← last good
2.29.1 6/6 ← first bad
2.30.0 6/6
2.34.0 6/6

The transition is sharp and clean: every version up to and including
2.29.0 stalls ~1/6; every version from 2.29.1 on stalls ~6/6. The only
command/stream change in 2.29.1 is cb061d2.

Reproduction conditions

The stall reproduces reliably only through a realistic streaming
workload
— a long-lived process reattached after resume that answers
several requests with sizeable, multi-chunk stdout responses (in
our case JSON-RPC tools/list / resources/list / prompts/list from an
stdio MCP server). The pattern observed: the first response after reattach
arrives, later ones never do.

A trivial synthetic process (one small line per second, or a single large
one-shot write) does not isolate the regression — those either stream
fine on both versions, or hit an unrelated single-write truncation that is
version-independent. So the reliable repro is the real stdio-server flow
above; we can share the exact harness on request.

Expected vs actual

  • Expected (≤ 2.29.0): after reattach, on_stdout keeps firing for
    every subsequent response; a full multi-request refresh completes.
  • Actual (≥ 2.29.1): after reattach, the stream delivers the first
    response (sometimes partially) then goes silent; subsequent responses
    never arrive, while a fresh reconnect shows the process still alive.

Impact

Any workload relying on a long-lived background process surviving
auto-pause and being reattached after resume (e.g. an stdio server behind
a gateway) loses output on nearly every resume from 2.29.1 on, forcing a
full fresh-sandbox rebuild as a workaround. On 2.29.0 this was rare
(~1/6); from 2.29.1 it is effectively every resume.

Environment

  • python-sdk 2.29.1 first bad, confirmed through 2.34.0; 2.29.0 last good
  • AsyncSandbox.create(lifecycle={"on_timeout": "pause", "auto_resume": True})
  • Reattach via AsyncSandbox.connect(sandbox_id=...) + commands.connect(pid=...)
  • Suspected change: cb061d2 (incremental stdout/stderr decode + stream teardown)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions