Skip to content

fix(cli): stop main() leaking SIGPIPE=SIG_DFL into the process (sy-6zq)#533

Merged
openclaw-dv merged 1 commit into
mainfrom
polecat/garnet-mpjy0gfm
May 24, 2026
Merged

fix(cli): stop main() leaking SIGPIPE=SIG_DFL into the process (sy-6zq)#533
openclaw-dv merged 1 commit into
mainfrom
polecat/garnet-mpjy0gfm

Conversation

@openclaw-dv
Copy link
Copy Markdown
Collaborator

Summary

main() installed SIGPIPE=SIG_DFL (so piped CLI output ends silently like a normal Unix tool) but never restored it. The SIGPIPE 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 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 via signal.getsignal() and restores it in a finally block. 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 to head, which is the case the SIG_DFL restore exists to handle.

Test plan

  • Full suite: 3039 passed, no exit 141, 87% coverage.
  • tests/test_broken_pipe.py + shim: 14 passed.
  • CLI piping (synthpanel … | head) stays silent and exits 0.
  • ruff clean.

References

…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>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying synthpanel with  Cloudflare Pages  Cloudflare Pages

Latest commit: e6bf5f7
Status: ✅  Deploy successful!
Preview URL: https://8e0bb9ec.synthpanel.pages.dev
Branch Preview URL: https://polecat-garnet-mpjy0gfm.synthpanel.pages.dev

View logs

@openclaw-dv openclaw-dv merged commit 648bd49 into main May 24, 2026
19 checks passed
@openclaw-dv openclaw-dv deleted the polecat/garnet-mpjy0gfm branch May 24, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant