fix(cli): stop main() leaking SIGPIPE=SIG_DFL into the process (sy-6zq)#533
Merged
Conversation
…q, sy-1n1) main() installed SIGPIPE=SIG_DFL (so piped CLI output ends silently like a normal Unix tool) but never restored it. The disposition is process-global, and main() is imported and called directly by ~10 tests plus library callers — so SIG_DFL leaked into the pytest process. A later unrelated broken-pipe write during output capture or coverage teardown was then delivered as SIGPIPE, silently killing the runner with exit 141. Which CI matrix entry died was non-deterministic (depends on test ordering), which is why the flake appeared to wander across Python 3.11/3.12/3.14 and blocked the merge queue. Fix has two layers: - main() snapshots the prior SIGPIPE handler (signal.getsignal) and restores it in the finally block. CLI piping behavior during the run is unchanged; the global disposition simply no longer outlives the invocation. - conftest.py adds an autouse fixture that restores SIGPIPE around every test, making this class of leak structurally impossible regardless of which code paths a test exercises (mirrors the existing network-block fixture). isatty() gating was rejected: stdout is not a tty exactly when piped to head, which is the case the SIG_DFL restore exists to handle. Verified: full suite 3039 passed (no exit 141, 87% cov); broken-pipe + shim tests 14 passed; CLI piping stays silent and exits 0; ruff clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deploying synthpanel with
|
| Latest commit: |
e6bf5f7
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://8e0bb9ec.synthpanel.pages.dev |
| Branch Preview URL: | https://polecat-garnet-mpjy0gfm.synthpanel.pages.dev |
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
main()installedSIGPIPE=SIG_DFL(so piped CLI output ends silently like a normal Unix tool) but never restored it. The SIGPIPE disposition is process-global, andmain()is imported and called directly by ~10 tests plus library callers — soSIG_DFLleaked into the pytest process. A later unrelated broken-pipe write during output capture or coverage teardown was then delivered as SIGPIPE, silently killing the CI runner with exit 141. Which matrix entry died was non-deterministic (depends on test ordering), which is why the flake wandered across Python 3.11/3.12/3.14 and intermittently blocked the merge queue.Changes
src/synth_panel/main.py:main()now snapshots the prior SIGPIPE handler viasignal.getsignal()and restores it in afinallyblock. CLI piping behavior during the run is unchanged; the global disposition simply no longer outlives the invocation.tests/conftest.py: adds an autouse fixture that restores SIGPIPE around every test, making this class of leak structurally impossible regardless of which code paths a test exercises (mirrors the existing network-block fixture).isatty()gating was rejected: stdout is not a tty exactly when piped tohead, which is the case theSIG_DFLrestore exists to handle.Test plan
tests/test_broken_pipe.py+ shim: 14 passed.synthpanel … | head) stays silent and exits 0.ruffclean.References