src/Transport/StdioTransport.php:72 loops over strpos($this->readBuffer, "\n") and only returns a decoded envelope when a newline is found in the buffer. When the upstream reader returns null to signal EOF at src/Transport/StdioTransport.php:89, the transport marks itself closed and either returns null immediately (if the buffer is empty) or continues the loop. On the next iteration there is still no newline, the closed branch fires, and the buffered partial line is discarded without ever being decoded. A peer that finishes its protocol exchange without a final \n therefore loses its last envelope — typically the terminal tool.result or session.close — even though the SDK accepts unterminated frames implicitly through CLI pipes.
The class docblock at src/Transport/StdioTransport.php:17 describes the framing as "Each envelope is encoded ... and appended with \n" but does not promise to require a terminator on read, and a strict-NDJSON implementation should still drain whatever remains in the buffer when EOF arrives.
Fix prompt: After the EOF path sets $this->closed = true, decode and return any non-empty residual content in $this->readBuffer exactly once before falling through to the return null close path. Add a unit test in tests/Unit/StdioTransportTest.php (creating it if necessary) that feeds a single encoded envelope without a trailing newline through StdioTransport::fromResources and asserts the envelope is returned before EOF.
src/Transport/StdioTransport.php:72loops overstrpos($this->readBuffer, "\n")and only returns a decoded envelope when a newline is found in the buffer. When the upstream reader returnsnullto signal EOF atsrc/Transport/StdioTransport.php:89, the transport marks itself closed and either returnsnullimmediately (if the buffer is empty) orcontinues the loop. On the next iteration there is still no newline, the closed branch fires, and the buffered partial line is discarded without ever being decoded. A peer that finishes its protocol exchange without a final\ntherefore loses its last envelope — typically the terminaltool.resultorsession.close— even though the SDK accepts unterminated frames implicitly through CLI pipes.The class docblock at
src/Transport/StdioTransport.php:17describes the framing as "Each envelope is encoded ... and appended with\n" but does not promise to require a terminator on read, and a strict-NDJSON implementation should still drain whatever remains in the buffer when EOF arrives.Fix prompt: After the EOF path sets
$this->closed = true, decode and return any non-empty residual content in$this->readBufferexactly once before falling through to thereturn nullclose path. Add a unit test intests/Unit/StdioTransportTest.php(creating it if necessary) that feeds a single encoded envelope without a trailing newline throughStdioTransport::fromResourcesand asserts the envelope is returned before EOF.