Am I using Io.failing wrong?

This test spins in an infinite loop:

test "failing Io fails to flush in positional mode" {
    var buffer: [128]u8 = undefined;
    var stdout: std.Io.File.Writer = .init(.stdout(), .failing, &buffer);
    try stdout.interface.writeAll("hello");
    const actual = stdout.interface.flush(); // spins CPU forever
    try std.testing.expectError(error.WriteFailed, actual);
}

Any other failing seem to work as expected:

test "failing Allocator fails" {
    const actual = std.mem.Allocator.failing.dupe(u8, "hello");
    try std.testing.expectError(error.OutOfMemory, actual);
}

test "failing Writer fails" {
    var writer = std.Io.Writer.failing;
    const actual = writer.writeAll("hello");
    try std.testing.expectError(error.WriteFailed, actual);
}

test "failing Io fails if used in streaming mode" {
    var buffer: [128]u8 = undefined;
    var stdout: std.Io.File.Writer = .initStreaming(.stdout(), .failing, &buffer);
    try stdout.interface.writeAll("hello");
    const actual = stdout.interface.flush();
    try std.testing.expectError(error.WriteFailed, actual);
    try std.testing.expectEqual(error.InputOutput, stdout.err.?);
}

test "failing Io fails in positional mode if the writer is unbuffered" {
    var stdout: std.Io.File.Writer = .init(.stdout(), .failing, &.{});
    const actual = stdout.interface.writeAll("hello");
    try std.testing.expectError(error.WriteFailed, actual);
    try std.testing.expectEqual(error.BrokenPipe, stdout.err.?);
}

The offending loop seems go like this:

// std.Io.Writer
pub fn flush(w: *Writer) Error!void {
    return w.vtable.flush(w);
}

// std.Io.Writer
pub fn defaultFlush(w: *Writer) Error!void {
    const drainFn = w.vtable.drain;
    while (w.end != 0) _ = try drainFn(w, &.{""}, 1);
}

// std.Io.File.Writer
pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
    const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
    switch (w.mode) {
        .positional, .positional_simple => return drainPositional(w, data, splat),
    // ... i cut irrelevant lines
}

// std.Io.File.Writer
fn drainPositional(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
    const io = w.io;
    const header = w.interface.buffered();
    const n = io.vtable.fileWritePositional(io.userdata, w.file, header, data, splat, w.pos) catch |err| switch (err) {
        // ... i cut here because n == 0
    };
    w.pos += n;
    return w.interface.consume(n); // consumes zero bytes
}

// std.Io
pub fn failingFileWritePositional(userdata: ?*anyopaque, file: File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize {
    _ = userdata;
    _ = file;
    _ = header;
    _ = offset;
    for (data[0 .. data.len - 1]) |item| {
        if (item.len > 0) return error.BrokenPipe;
    }
    if (data[data.len - 1].len != 0 and splat != 0) return error.BrokenPipe;
    return 0; // <<<<<<<======= why???
}

So it turns out failing Io reports 0 bytes successfully written when the data are only in the header. Why? Then flush() sees that the buffer is not empty and tries again.

Am I doing it backwards? Is it a bug in the implementation of Io.failing? Is it a bug in Io interface design that does not allow to return an error there? Is it a bug in flush() implementation? Is it a bug in Io.Writer interface design that forces flush() to loop until the buffer is empty?

probably wanted to make writes of 0 data not fail for some reason and forgot to check the header.

Should I file a bug report?

https://codeberg.org/ziglang/zig/issues/37406