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.
Bug Description
CommandHandlein the JS SDK advertisesonStdout,onStderr, andonPtycallbacks that may returnPromise<void>, buthandleEvents()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 bypassiterationErrorbecause they are detached from thetry/catch.Root Cause
packages/js-sdk/src/sandbox/commands/commandHandle.tsL149-164:Steps to Reproduce
Expected Behavior
wait()should resolve only after all async callbacks have completed, matching the Python async SDK'sinspect.isawaitablehandling.Suggested Fix
Await callback results when they return promises:
This keeps
wait()aligned with the documented callback types and ensures callback failures surface throughwait()instead of becoming unhandled rejections.