fix(sdk): watch handle callback awaiting, request timeouts, and exit semantics#1436
fix(sdk): watch handle callback awaiting, request timeouts, and exit semantics#1436mishushakov wants to merge 6 commits into
Conversation
- JS: await async onEvent/onExit callbacks in WatchHandle so events are handled sequentially and callback errors are routed to onExit - JS: report a clean exit (no error) to onExit when the watch is stopped via stop() instead of a misleading TimeoutError - Python sync: apply request timeout and user auth headers to WatchHandle.get_new_events/stop and accept a request_timeout param - Python async: invoke on_exit whenever the watching ends — with the exception on stream failure, or None on normal stream end and stop() - Tests: unit tests for all three handles, secured-envd watch coverage for JS, fixed async secured-envd test to actually use a secure sandbox Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 4f48d20 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
PR SummaryMedium Risk Overview JS Python async Python sync JS start-stream helpers throw Adds unit tests for handles and start events, plus secured-envd watch coverage (JS integration; Python async test fixed to use a secure sandbox). Reviewed by Cursor Bugbot for commit 4f48d20. Bugbot is set up for automated code reviews on this repo. Configure here. |
Package ArtifactsBuilt from 9107a22. Download artifacts from this workflow run. JS SDK ( npm install ./e2b-2.30.1-mishushakov-fix-watchhandle-callbacks-timeout.0.tgzCLI ( npm install ./e2b-cli-2.11.2-mishushakov-fix-watchhandle-callbacks-timeout.0.tgzPython SDK ( pip install ./e2b-2.29.0+mishushakov.fix.watchhandle.callbacks.timeout-py3-none-any.whl |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bfb9c5bd8f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- JS: report onEvent callback errors to onExit even when stop() was requested while the callback was in flight — only the stream abort caused by stop() is treated as a clean exit - Python async: stop() now re-raises exceptions raised by on_exit instead of silently swallowing them via asyncio.wait Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
stop() now resolves only after the watching has fully ended and the onExit callback has completed, re-throwing errors raised by onExit. An in-flight onEvent callback is abandoned on stop — the JS equivalent of the cancelled handler task in the Python SDK — which also prevents a deadlock when stop() is awaited from inside onEvent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Guard against an empty process/watch start stream where `.next().value` is `undefined`. The optional chain was on `.event` rather than the `startEvent` itself, so an exhausted stream slipped past the guard and surfaced as a bare `TypeError: Cannot read properties of undefined` instead of the intended `Expected start event` error. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t stop Promise.race could pick the stop over an onEvent callback that rejected in the same tick, swallowing the error and calling onExit(undefined). Track the callback's settlement so a settled (resolved/rejected) callback always wins over a concurrent stop(); only a still-pending callback is abandoned. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…handle-callbacks-timeout # Conflicts: # packages/python-sdk/e2b/sandbox_async/filesystem/watch_handle.py # packages/python-sdk/e2b/sandbox_sync/filesystem/filesystem.py # packages/python-sdk/e2b/sandbox_sync/filesystem/watch_handle.py
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 4f48d20. Configure here.
| this.stopped = true | ||
| this.notifyStopped() | ||
| this.handleStop() | ||
| await this.handlingEvents |
There was a problem hiding this comment.
Reentrant stop deadlocks event loop
High Severity
WatchHandle.stop() now awaits handlingEvents, so calling stop() (or await stop()) from a synchronous onEvent handler or from onExit while handleEvents is still inside those callbacks deadlocks: stop() waits on handlingEvents, which cannot finish until the callback returns.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 4f48d20. Configure here.


Description
Fixes several
WatchHandleissues across the SDKs and aligns the watch lifecycle contract between JS and Python:onEvent/onExitcallbacks are now awaited (sequential event handling, callback errors routed toonExit).stop()reports a clean exit instead of a misleadingTimeoutError, and now resolves only after the watching has fully ended andonExithas completed — re-throwing errors raised byonExit. An in-flightonEventcallback is abandoned on stop (the JS equivalent of Python's cancelled handler task), which also prevents a deadlock whenstop()is awaited from insideonEvent.get_new_events()/stop()now apply the default request timeout and the user auth headers (previously unbounded and sent as the default user) and accept arequest_timeoutparameter.on_exitis now invoked whenever the watching ends — with the exception on stream failure, orNoneon normal stream end and onstop()— andstop()re-raises exceptions fromon_exitinstead of silently swallowing them.The resulting contract is identical in both SDKs: the exit callback fires exactly once however the watching ends (
None/undefinedon normal end or user stop, the error otherwise), andstop()returns only after exit handling has completed.Includes unit tests for all three handles, a secured-envd
watchDirtest for JS, and a fix for the async secured-envd test which wasn't actually using a secure sandbox.Usage
🤖 Generated with Claude Code