Skip to content

JS SDK wait() resolves before async stdout/stderr/PTY callbacks finish #1259

@YizukiAme

Description

@YizukiAme

Bug Description

CommandHandle in the JS SDK advertises onStdout, onStderr, and onPty callbacks that may return Promise<void>, but handleEvents() invokes them without awaiting. As a result, wait() only waits for the event stream to end, not for user-provided async handlers to finish.

Any state those handlers persist can still be in flight after await handle.wait() returns, and rejected callback promises bypass iterationError because they are detached from the try/catch.

Root Cause

packages/js-sdk/src/sandbox/commands/commandHandle.ts L149-164:

constructor(
  readonly pid: number,
  ...
  private readonly onStdout?: (stdout: string) => void | Promise<void>,
  private readonly onStderr?: (stderr: string) => void | Promise<void>,
  private readonly onPty?: (pty: Uint8Array) => void | Promise<void>
) {
  this._wait = this.handleEvents()
}

// In handleEvents():
if (stdout !== null) {
  this.onStdout?.(stdout)      // ← not awaited, even though it can return Promise
} else if (stderr !== null) {
  this.onStderr?.(stderr)      // ← not awaited
} else if (pty) {
  this.onPty?.(pty)            // ← not awaited
}

Steps to Reproduce

const handle = await sandbox.commands.run("echo hello", {
  onStdout: async (data) => {
    await saveToDatabase(data); // async operation
  }
});
await handle.wait();
// At this point, saveToDatabase() may still be running!

Expected Behavior

wait() should resolve only after all async callbacks have completed, matching the Python async SDK's inspect.isawaitable handling.

Suggested Fix

Await callback results when they return promises:

if (stdout !== null) {
  await this.onStdout?.(stdout)
}

This keeps wait() aligned with the documented callback types and ensures callback failures surface through wait() instead of becoming unhandled rejections.

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