fix(cli): silent SIGPIPE termination instead of BrokenPipeError traceback#13
Merged
Conversation
…back
Piping bcli output to a consumer that closes early (e.g. `head -5`,
`grep -m 1`, scripted truncation) caused Python's interpreter teardown
to flush stdout, hit EPIPE, and emit:
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' ...>
BrokenPipeError: [Errno 32] Broken pipe
— scary-looking noise on stderr for what's actually normal Unix
behaviour. cat, grep, ls, etc. all exit silently in this case.
Introduce a new console-script entry point `bcli_cli.app:main` that
restores the POSIX default for SIGPIPE before invoking the Typer app.
On platforms without SIGPIPE (Windows), a `BrokenPipeError` safety net
catches the same condition and closes stdout/stderr before exiting so
the atexit flush can't re-trigger it.
The pyproject `[project.scripts] bcli` entry updates to
`bcli_cli.app:main`; the `app` object remains exported and importable
for tests and `bcli-mcp`.
Adds a subprocess regression test that runs the entry point and pipes
into `head -1`, asserting the resulting stderr contains no
`BrokenPipeError` or `Exception ignored` markers.
igor-ctrl
added a commit
that referenced
this pull request
May 12, 2026
The Typer root-callback (introduced in #10) shared the name `main` with the console-script entry point (added in #13), tripping ruff F811 (redefinition) when CI lint runs against the rebased history. The callback's name is arbitrary — Typer wires it via the `@app.callback()` decorator, not by symbol lookup — so renaming to `_root_callback` resolves the redefinition without behavior change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Piping
bclioutput to a truncating consumer —bcli ... | head -5,| grep -m 1, etc. — emits a Python traceback at interpreter shutdown:Scary-looking but harmless — Python's atexit stdout-flush hits EPIPE because the downstream pipe is already closed.
cat,grep,lsand other well-behaved Unix tools handle this silently via SIGPIPE.Fix
A new console-script entry point
bcli_cli.app:maininstalls the POSIX default SIGPIPE handler (SIG_DFL) before invoking the Typer app. On Windows (no SIGPIPE constant), aBrokenPipeErrorsafety net closes stdout/stderr and exits 0 so the atexit flush can't re-trigger.pyproject.toml— entry point changes frombcli_cli.app:apptobcli_cli.app:main.appremains exported and used directly by tests andbcli-mcp.src/bcli_cli/app.py— adds themain()wrapper, ~25 lines.Test plan
tests/test_cli/test_pipe_handling.pyruns the entry point in a subprocess, pipesbcli --helptohead -1, asserts stderr contains noBrokenPipeErrororException ignoredmarkers.uv run pytest tests/— 526 pass, 5 skipped.bcli --profile finance-sandbox endpoint list | head -5shows the table and exits cleanly, no traceback.bcli-beautech-installer's install verify step.