Add end_session_confirm_message handshake#31
Conversation
Previously the Python client sent end_session_message and immediately closed the WS. Any artifact_upload_request_message the runtime produced after that (from chokidar events that fired post-terminal, e.g. files created by post-agent commands) was sent on a half-closed socket and the upload_response could never make it back. New protocol: - Runtime receives end_session_message, drains the artifact watcher, sends end_session_confirm_message, then closes. - Python client sends end_session_message, then pumps the receive loop — handling late artifact_upload_request_messages with the session's most-recent callback — until confirm arrives (or timeout). Breaking change: older clients that close without waiting for confirm will no longer get late artifacts uploaded. Both packages bumped to 0.12.0.
d94c8db to
612fdd3
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit bdf9873. Configure here.
| try: | ||
| yield runtime_session | ||
| finally: | ||
| await runtime_session._end_session() |
There was a problem hiding this comment.
Unprotected cleanup in finally may suppress user exceptions
Medium Severity
_end_session() is called in a bare finally block without a try/except wrapper. While end_session() in the transport catches TimeoutError and ConnectionClosed, it does not catch all exceptions — for example, if the server sends valid JSON that isn't an object (e.g., an array), msg.get("message_type") raises AttributeError, which propagates uncaught. If the user's code also raised an exception, the cleanup exception from _end_session() would suppress the original error, making debugging very difficult.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit bdf9873. Configure here.


Summary
Previously the Python client sent
end_session_messageand immediately closed the WS. Anyartifact_upload_request_messagethe runtime produced after that (from chokidar events that fired post-terminal — e.g. files created by post-agent commands like `zip`) was sent on a half-closed socket and the upload response could never make it back. Symptom:```
Executing post-agent command: zip ...
Received new WS message
Received end_session_message. Closing session.
Waiting 3000ms for artifact watcher to drain...
WebSocket connection closed ← client closed while we were draining
Requesting upload for artifact: deterministic-test.zip ← dropped
Waiting for 1 artifact round-trips... ← hangs until timeout
```
New protocol:
Bumps both packages to 0.12.0. No backwards compatibility.
Test plan
Note
Medium Risk
Introduces a new WebSocket protocol handshake and changes session teardown semantics across both the TS runtime and Python client, which can break compatibility with older peers and affect connection lifecycle behavior.
Overview
Adds an
end_session_confirm_messagehandshake so sessions close gracefully: the runtime drains the artifact watcher after receivingend_session_message, then confirms and closes, allowing lateartifact_upload_request_messages to complete.Updates the Python persistent-session flow to send
end_session_messageon context exit and keep pumping messages (responding to late artifact upload requests using the most-recent callback) until confirmation or timeout; transports now expose a newend_session()API to support this drain/confirm loop.Bumps package versions to
0.13.0(Python and Node) and updates test fakes to implement the newend_sessionmethod.Reviewed by Cursor Bugbot for commit bdf9873. Bugbot is set up for automated code reviews on this repo. Configure here.