Skip to content

wasip2 CLI stdio discards host io::Error kind, surfaces EIO instead of EPIPE/EISDIR #14061

Description

@eduardomourar

Test Case

No wasm file needed — reproducible with any wasip2 guest that writes past a closed pipe, or reads stdin redirected from a directory. Minimal repro source: [gist-equivalent below].

// write side
use std::io::Write;
fn main() {
    loop {
        if let Err(e) = std::io::stdout().write_all(b"0123456789\n") {
            eprintln!("write error: kind={:?} raw_os_error={:?}", e.kind(), e.raw_os_error());
            return;
        }
    }
}
// read side
use std::io::Read;
fn main() {
    let mut buf = [0u8; 16];
    match std::io::stdin().read(&mut buf) {
        Err(e) => eprintln!("stdin read error: kind={:?} raw_os_error={:?}", e.kind(), e.raw_os_error()),
        _ => {}
    }
}

Steps to Reproduce

  • Compile the write-side repro for wasm32-wasip2, run under wasmtime run pipe.wasm | head -c 30, so the reader closes early.
  • Compile the read-side repro for wasm32-wasip2, run under wasmtime run --dir <dir> stdin.wasm < <dir>, redirecting stdin from a directory.
  • Compare against the same repros compiled for wasm32-wasip1.

Expected Results

wasip1 correctly reports BrokenPipe/EPIPE(64) for the write case and IsADirectory/EISDIR(31) for the read case — matching native Unix.

Actual Results

wasip2 reports InputOutputError/EIO(29) for both cases. Confirmed on wasmtime 45.0.0 and 47.0.3 (latest release, 2026-07-31) — unchanged between versions.

Root cause (traced through wasi-libc + wasmtime source):

This affects every wasip2 guest, not just Rust's — e.g. uutils/coreutils#13625 had to add a userland wasi_is_broken_pipe helper checking for raw errno 29 as a workaround.

Versions and Environment

Wasmtime version or commit: 45.0.0 and 47.0.3 (both exhibit the bug)

Operating system: macOS (Darwin), confirmed reproducible; likely platform-independent since the bug is in wasmtime's stdio glue rather than any OS-specific path

Architecture: aarch64 host, wasm32-wasip2 guest

Extra Info

The wasi:io/streams spec's stream-error type is designed for exactly this: it carries an opaque error resource that other interfaces can "downcast" into more specific info (e.g. wasi:filesystem/types/filesystem-error-code). A fix could either (a) have StdioOutputStream::write/WasiStdin::read check e.kind() and return StreamError::Closed for BrokenPipe (matching what wasi-libc already expects), or (b) give p2's CLI stdio the same io::Error-recovery path p1 already has via filesystem::ErrorCode::from.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugIncorrect behavior in the current implementation that needs fixing

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions