Skip to content

fix(sdk): correct filesystem watch handle callback and timeout behavior#1480

Open
mishushakov wants to merge 1 commit into
mainfrom
mishushakov/fix-watch-handle-on-exit
Open

fix(sdk): correct filesystem watch handle callback and timeout behavior#1480
mishushakov wants to merge 1 commit into
mainfrom
mishushakov/fix-watch-handle-on-exit

Conversation

@mishushakov

Copy link
Copy Markdown
Member

Fixes three filesystem watch-handle bugs across the JS and Python SDKs. JS WatchHandle now awaits async onEvent/onExit callbacks so a rejecting onEvent is routed to onExit and stops the watch instead of crashing Node with an unhandled rejection — onExit fires exactly once (no argument on a clean end, the error on failure) and a throwing onExit can no longer leak. Sync Python WatchHandle.get_new_events()/stop() now send a request timeout (default 60s, overridable) and the authentication header, so the polling/stop RPCs can't hang the thread forever or be sent unauthenticated on older envd. Async Python AsyncWatchHandle now invokes on_exit on a clean stream end (None) and on stop() in addition to on error — matching the JS SDK — and swallows errors raised by on_exit to avoid unretrieved task exceptions. Added unit tests for all three paths and a changeset (patch for e2b + @e2b/python-sdk).

Usage

# Python (sync): the polling/stop calls now accept a request timeout
handle = sandbox.files.watch_dir("/home/user")
events = handle.get_new_events(request_timeout=10)
handle.stop(request_timeout=10)
# Python (async): on_exit now fires on a clean end / stop() (err is None), not only on error
async def on_exit(err: Optional[Exception]):
    print("watch ended", "cleanly" if err is None else f"with {err}")

handle = await sandbox.files.watch_dir("/home/user", on_event=on_event, on_exit=on_exit)
await handle.stop()  # -> on_exit(None)
// JS: an async onEvent that throws is surfaced via onExit instead of crashing the process
await sandbox.files.watchDir("/home/user", async (event) => {
  await handle(event) // a rejection here now ends the watch and calls onExit(err)
}, {
  onExit: (err) => console.log("watch ended", err ?? "cleanly"),
})

🤖 Generated with Claude Code

Fixes three filesystem watch handle bugs across the JS and Python SDKs:

- JS WatchHandle now awaits async onEvent/onExit callbacks so a rejecting
  onEvent is routed to onExit and stops the watch instead of crashing Node
  with an unhandled rejection. onExit fires exactly once (no arg on a clean
  end, the error on failure) and a throwing onExit can no longer leak.
- Sync Python WatchHandle.get_new_events()/stop() now send a request timeout
  (default 60s, overridable) and the authentication header, so the polling
  and stop RPCs can't hang forever or be sent unauthenticated on older envd.
- Async Python AsyncWatchHandle now invokes on_exit on a clean stream end
  (None) and on stop(), in addition to on error, matching the JS SDK, and
  swallows errors raised by on_exit to avoid unretrieved task exceptions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@cursor

cursor Bot commented Jun 24, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Targeted callback and RPC-option fixes in filesystem watch handles with new unit tests; no broad API redesign beyond documented on_exit semantics and optional request_timeout on sync poll/stop.

Overview
JS WatchHandle: onEvent and onExit were not awaited, so async rejections could crash Node and onExit could run before onEvent finished. The handler now awaits them, calls onExit once (error or clean end), routes a failing onEvent through that path, and swallows errors from onExit.

Sync Python WatchHandle: get_new_events() and stop() did not pass request_timeout or auth headers, so polls could hang and fail on older envd. Those RPCs now use the connection config timeout and authentication_header; the handle stores config, envd version, and user from watch_dir.

Async Python AsyncWatchHandle: on_exit only ran on stream errors, not on a normal end or stop(). It now runs once with None or the error (including cancel from stop()), with errors from on_exit swallowed in the background task. on_exit is typed as Optional[Exception].

Reviewed by Cursor Bugbot for commit c3930bc. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot

changeset-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c3930bc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@e2b/python-sdk Patch
e2b Patch

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

@github-actions

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from ef5e0de. Download artifacts from this workflow run.

JS SDK (e2b@2.30.6-mishushakov-fix-watch-handle-on-exit.0):

npm install ./e2b-2.30.6-mishushakov-fix-watch-handle-on-exit.0.tgz

CLI (@e2b/cli@2.12.3-mishushakov-fix-watch-handle-on-exit.0):

npm install ./e2b-cli-2.12.3-mishushakov-fix-watch-handle-on-exit.0.tgz

Python SDK (e2b==2.29.5+mishushakov-fix-watch-handle-on-exit):

pip install ./e2b-2.29.5+mishushakov.fix.watch.handle.on.exit-py3-none-any.whl

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant