shell: throw instead of panicking when a ReadableStream is used as a redirect

Closed
JA
19d ago
+23-18

What

await $`cat < ${new ReadableStream({...})}`;

crashed the process with panic: TODO SHELL READABLE STREAM (src/runtime/shell/states/Cmd.rs). The template parser explicitly accepted ReadableStream values into jsobjs, and the redirect setup then hit a TODO panic that has been there since the Zig shell.

Streaming a ReadableStream into a shell command's stdin (subprocess and builtins) isn't implemented, so this rejects it where the template is parsed: the $ tag throws synchronously with

ReadableStream is not supported as a shell redirect yet. Buffer it first, e.g. < ${await new Response(stream).blob()}, or use Bun.spawn({ stdin: stream })

Throwing at parse time (rather than from Cmd/Builtin redirect init) matters: a throw from redirect init only becomes a rejection when it happens synchronously under run_from_js; when the command starts from an async resume (sleep 0.01; cat < ${stream}) the exception would be stranded with no JS frame.

Blob / Bun.file / Response / ArrayBuffer redirects are unchanged.

Test

test/js/bun/shell/bunshell.test.ts — "redirect stdin from a ReadableStream throws instead of crashing" covers a subprocess (cat, bun -e), a builtin (echo), and an async-resumed command (true && cat), plus the documented Response.blob() workaround. Crashes with USE_SYSTEM_BUN=1, passes on the debug build (also with BUN_ENABLE_EXPERIMENTAL_SHELL_BUILTINS=1).

Follow-up (not in this PR): actually plumb Stdio::ReadableStream through the shell's Writable (FileSink + assign_to_stream, as Bun.spawn does) and give builtins an async stdin source.