Skip to content

fix(cli): treat stdout broken pipe as success - #366

Open
SebTardif wants to merge 2 commits into
openclaw:mainfrom
SebTardif:fix/stdout-broken-pipe
Open

fix(cli): treat stdout broken pipe as success#366
SebTardif wants to merge 2 commits into
openclaw:mainfrom
SebTardif:fix/stdout-broken-pipe

Conversation

@SebTardif

Copy link
Copy Markdown
Contributor

JSON success output from wacli should not fail the process when the downstream reader goes away.

What Problem This Solves

internal/out.WriteJSON returns the error from fmt.Fprintln on stdout. cmd/wacli/main.go then calls os.Exit(1) on any execute error. A successful command that writes JSON to a closed pipe (for example wacli accounts list | head) can therefore exit 1 even though the listing itself succeeded.

WriteError already ignores write errors on stderr. The success path did not.

This is the same class of CLI contract GitHub CLI already adopted: ignore EPIPE when the pager or pipe reader closes (cli/cli#5143).

Why

The command work is already done when WriteJSON runs. EPIPE means the reader left, not that wacli failed. Propagating that write error makes pipelines and head look like product failures.

The helper treats syscall.EPIPE as success and also unwraps *fs.PathError / *os.PathError so the usual write |1: broken pipe form is covered. Other write errors (for example disk full) still return.

User Impact

Scripts and interactive pipes that stop reading early no longer get a false non-zero status from an otherwise successful JSON command. set -e and CI steps that pipe wacli --json into head, jq, or a pager stop failing for a closed stdout.

Evidence

Call chain: accounts list / chats list / messages list (and every other JSON command) -> out.WriteJSON(os.Stdout, ...) -> fmt.Fprintln -> execute error -> os.Exit(1) in cmd/wacli/main.go.

The write error was introduced with the original output helper in 8b725754b368 (2025-12-12, 249 days ago).

Live closed pipe, unfixed WriteJSON (reader closed, then write):

$ go run ./internal/out/epipe_proof.go
WriteJSON err=write |1: broken pipe
exit=1

Live closed pipe after this patch, same program:

$ go run ./internal/out/epipe_proof.go
WriteJSON err=<nil>
exit=0

The program creates an os.Pipe, closes the read end, then calls out.WriteJSON on the write end. That is a real kernel EPIPE (write |1: broken pipe), not a stubbed errno.

Related:

  • Same-repo: no open or closed PR already treats stdout EPIPE as success. Not the store-upsert work in #328.
  • External: cli/cli#5143 (ignore EPIPE when a pager closes).

Real behavior proof

  • Behavior or issue addressed: Successful JSON writes to a closed stdout pipe returned EPIPE, so wacli exited 1 after a completed listing.

  • Real environment tested: macOS, Go go1.26.6, branch fix/stdout-broken-pipe at this patch, /tmp/pr-wacli-epipe.

  • Exact steps or command run after this patch: Restored unfixed WriteJSON from upstream/main, ran a go run program that writes JSON to a closed os.Pipe, recorded the error. Restored this patch and ran the same program again.

  • Evidence after fix: terminal output from the closed-pipe program:

    Unfixed tree:

    $ go run ./internal/out/epipe_proof.go
    WriteJSON err=write |1: broken pipe
    exit=1

    After this patch:

    $ go run ./internal/out/epipe_proof.go
    WriteJSON err=<nil>
    exit=0
  • Observed result after fix: The same closed-pipe write now returns nil, so execute does not trip os.Exit(1). Other write errors are still returned.

  • What was not tested: Windows WSAECONNRESET (no existing helper in-tree). A live WhatsApp-backed accounts list | head against a paired store.

Summary

WriteJSON now treats broken pipe as success. WriteError is unchanged. Store upsert paths are not touched.

WriteJSON returned EPIPE to main, so pipes like wacli accounts
list | head exited 1 after a successful listing. Ignore broken
pipe on the JSON success write, matching WriteError.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@clawsweeper

clawsweeper Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Aug 19, 2026
@clawsweeper

clawsweeper Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed August 23, 2026, 10:02 AM ET / 14:02 UTC.

ClawSweeper review

What this changes

The PR makes successful JSON output ignore closed stdout pipes on Unix and Windows while preserving unrelated write errors.

Merge readiness

⚠️ Ready for maintainer review - 1 item remains

Keep open for normal maintainer merge review: current main and v0.17.1 still return stdout write errors, while this focused patch makes expected closed-pipe errors successful and retains other failures.

Priority: P2
Reviewed head: ed36dfe40147525b53083065ae75cce8bf610ea7

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) This is a focused, tested cross-platform repair with credible after-fix terminal evidence.
Proof confidence 🐚 platinum hermit (4/6) Sufficient (terminal): The PR body provides before-and-after terminal output from a real closed kernel pipe, showing the after-fix write returns nil and exits zero.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The PR body provides before-and-after terminal output from a real closed kernel pipe, showing the after-fix write returns nil and exits zero.
Evidence reviewed 6 items Current released behavior remains unfixed: The current main implementation returns the error from the JSON stdout write directly; the CLI root returns command errors and main exits nonzero.
Patch is narrowly placed at the output boundary: The PR normalizes only recognized closed-pipe errors after JSON serialization, leaving JSON marshal failures and other writer errors unchanged.
Focused regression coverage: Tests cover Unix EPIPE, wrapped path errors, preserved non-pipe errors, and Windows broken-pipe/no-data variants.
Findings None None.
Security None None.

Live Verification

Command: pnpm wacli -- --help | head -n 1

Result: PASS (completed)

pnpm wacli -- --help | head -n 1
runner@runnervm76f27:/tmp/clawsweeper-live-proof-366-Xd2MEN/target$ pnpm wacli -- --help | head -n 1
$ bash -lc 'pnpm -s build ›/dev/null && exec ./dist/wacli "$@"' _ -- --help
pnpm wacli -- --help | head -n 1
$ mkdir -p dist && CGO_ENABLED=1 CGO_CFLAGS="${CGO_CFLAGS:+$CGO_CFLAGS }-Wno-error=missing-braces" go build -tags sqlite_fts5 -o dist/wacli ./cmd/wacli
go: downloading go1.26.6 (linux/amd64)













































Assertions:

  • PASS expect_output: wacli

How this fits together

wacli commands serialize successful results through the output package before the CLI returns an exit status. That output is consumed by terminals, pipes, and scripts, so a stdout write error can otherwise turn a completed command into a failure.

flowchart LR
  A[Completed CLI command] --> B[JSON output formatter]
  B --> C[Write to stdout]
  C --> D{Closed pipe?}
  D -->|Yes| E[Return success]
  D -->|No| F[Return write error]
  E --> G[Exit zero]
  F --> H[Exit nonzero]
Loading

Before merge

  • Complete next step (P2) - No concrete repair remains; this PR is ready for normal maintainer merge gating.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production and regression-test scope production +73, tests +95 across 7 files The platform-specific behavior is accompanied by more focused regression coverage than production code.

Technical review

Best possible solution:

Merge the focused output-layer handling so completed JSON commands remain successful when a downstream reader intentionally closes stdout.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main returns the stdout write error through the command root to main's nonzero exit path, and the PR supplies an after-fix real closed-kernel-pipe result.

Is this the best way to solve the issue?

Yes. Handling only recognized closed-pipe errors at the shared JSON output boundary is the narrowest maintainable repair and preserves unrelated write failures.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 97e14efdf91a.

Labels

Label justifications:

  • P2: This fixes a bounded but real CLI scripting failure without affecting message delivery, stored data, or authentication.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body provides before-and-after terminal output from a real closed kernel pipe, showing the after-fix write returns nil and exits zero.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides before-and-after terminal output from a real closed kernel pipe, showing the after-fix write returns nil and exits zero.

Evidence

What I checked:

  • Current released behavior remains unfixed: The current main implementation returns the error from the JSON stdout write directly; the CLI root returns command errors and main exits nonzero. (internal/out/out.go:20, 97e14efdf91a)
  • Patch is narrowly placed at the output boundary: The PR normalizes only recognized closed-pipe errors after JSON serialization, leaving JSON marshal failures and other writer errors unchanged. (internal/out/out.go:23, ed36dfe40147)
  • Focused regression coverage: Tests cover Unix EPIPE, wrapped path errors, preserved non-pipe errors, and Windows broken-pipe/no-data variants. (internal/out/out_test.go:41, ed36dfe40147)
  • Output-path provenance: Current-main blame attributes the existing WriteJSON return path to Peter Steinberger's release preparation, and the repository's initial wacli commit is authored by Peter Steinberger. (internal/out/out.go:20, 8b725754b368)
  • Release and branch status: v0.17.1 points at the current main SHA, while no release tag contains the PR head; the proposed behavior is not already shipped. (internal/out/out.go:20, 97e14efdf91a)
  • Supply-chain scope: The Windows helper imports golang.org/x/sys/windows, but golang.org/x/sys was already a direct module dependency before this branch and no dependency files change. (go.mod:11, 97e14efdf91a)

Likely related people:

  • Peter Steinberger: The current-main blame trail for the affected return path and the initial wacli commit both point to Peter Steinberger. (role: original output-path author and recent line contributor; confidence: medium; commits: 8b725754b368, 97e14efdf91a; files: internal/out/out.go, cmd/wacli/main.go)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (2 earlier review cycles)
  • reviewed 2026-08-19T00:15:19.815Z sha 76dc61e :: needs changes before merge. :: [P2] Handle Windows closed-pipe errors | [P3] Remove the release-owned changelog entry
  • reviewed 2026-08-20T00:47:59.193Z sha ed36dfe :: needs maintainer review before merge. :: none

Recognize ERROR_BROKEN_PIPE and ERROR_NO_DATA in addition to
Unix EPIPE. Leave CHANGELOG.md to the release workflow.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@SebTardif

Copy link
Copy Markdown
Contributor Author

@clawsweeper

Handle Windows closed-pipe errors
syscall.EPIPE does not cover ERROR_BROKEN_PIPE / ERROR_NO_DATA

Fixed on ed36dfe. Unix still matches EPIPE. Windows matches ERROR_BROKEN_PIPE (109) and ERROR_NO_DATA (232), including PathError unwrap. The shared errno helper is covered on this host. WriteJSON Windows cases compile with GOOS=windows.

Remove the release-owned changelog entry

Dropped the Unreleased changelog hunk in the same commit.

@clawsweeper re-review

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant