A closed pipe on stdout is reported as 'zfj: WriteFailed' and exit 3 #34

Closed
opened 2026-10-17 10:36:02 -05:00 by jeff · 0 comments
Owner

When whatever reads zfj's stdout closes the pipe before zfj has finished writing, zfj prints zfj: WriteFailed to stderr and exits 3, the exit status for a broken environment.

$ zfj run view 43 --log -j release | head -2
── release (attempt 1) ──
2026-10-15T21:51:47.1818301Z Runner … received task 8718 of job release, triggered by event: push
zfj: WriteFailed
$ echo ${PIPESTATUS[0]}
3

It only happens when the output is larger than the pipe's buffer (64 KiB on Linux): smaller output has all been written before head exits, so zfj issue list | head -1 is quiet. That makes it most likely with run view --log, --paginate and op responses, but every command writes through the same ctx.stdout, so none is exempt.

Why

Zig ignores SIGPIPE, so the write fails with EPIPE, Io.Writer reports that as error.WriteFailed, and the catch-all in src/cli/main.zig (run(...) catch |err| ctx.environment("{s}", .{@errorName(err)})) turns it into an environment error.

What it should do

A reader going away is not a failure of zfj's, and cmd | head is ordinary use, so it should end quietly: no message on stderr. Other tools differ on the status — git dies of SIGPIPE (141 from the shell), gh exits 0 — and the choice needs making. Exiting 0 keeps set -o pipefail scripts that use | head working.

The writer's underlying error has to be checked rather than WriteFailed alone, so that a full disk or an I/O error on a redirect to a file is still reported.

When whatever reads zfj's stdout closes the pipe before zfj has finished writing, zfj prints `zfj: WriteFailed` to stderr and exits 3, the exit status for a broken environment. ```console $ zfj run view 43 --log -j release | head -2 ── release (attempt 1) ── 2026-10-15T21:51:47.1818301Z Runner … received task 8718 of job release, triggered by event: push zfj: WriteFailed $ echo ${PIPESTATUS[0]} 3 ``` It only happens when the output is larger than the pipe's buffer (64 KiB on Linux): smaller output has all been written before `head` exits, so `zfj issue list | head -1` is quiet. That makes it most likely with `run view --log`, `--paginate` and `op` responses, but every command writes through the same `ctx.stdout`, so none is exempt. ## Why Zig ignores `SIGPIPE`, so the write fails with `EPIPE`, `Io.Writer` reports that as `error.WriteFailed`, and the catch-all in `src/cli/main.zig` (`run(...) catch |err| ctx.environment("{s}", .{@errorName(err)})`) turns it into an environment error. ## What it should do A reader going away is not a failure of zfj's, and `cmd | head` is ordinary use, so it should end quietly: no message on stderr. Other tools differ on the status — `git` dies of `SIGPIPE` (141 from the shell), `gh` exits 0 — and the choice needs making. Exiting 0 keeps `set -o pipefail` scripts that use `| head` working. The writer's underlying error has to be checked rather than `WriteFailed` alone, so that a full disk or an I/O error on a redirect to a file is still reported.
jeff closed this issue 2026-10-17 13:58:08 -05:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jeff/zfj#34
No description provided.