CLI: a closed stdout pipe exits 141 silently (was E_INTERNAL); example avoids head under pipefail
ci / test (3.12) (push) Failing after 8s
ci / test (3.13) (push) Failing after 10s

Found by the P10 fresh-VM run: `simurg inspect … | head` raised BrokenPipeError, reported as
an internal error, and stopped examples/gsa_demo.sh (set -o pipefail). run_guarded now flushes
stdout inside the guard and maps BrokenPipeError to ExitCode.SIGPIPE (141) without a message.
p10.sh: SKIP_DOCKER/SKIP_CONDA, clearer digest report; p10t.sh runs build+pip+tutorial only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01814GKVzZVL3FuF3DeiZXhv
This commit is contained in:
kemalandClaude Opus 5.5 committed 2026-09-28 23:14:47 +01:00
1 parent 92416397fe
commit bc93e00318
6 files changed
+38 -6

No files matched your search

+6
View File
@@ -7,6 +7,7 @@ Later phases add the other commands of CLI-001.
from __future__ import annotations
import json
import os
import sys
from collections.abc import Callable
from pathlib import Path
@@ -58,6 +59,11 @@ def run_guarded(reporter: Reporter, fn: Callable[[], int]) -> None:
code = ExitCode.OK
try:
code = ExitCode(fn())
sys.stdout.flush() # surface a closed pipe here, not at interpreter exit
except BrokenPipeError:
# the consumer of stdout stopped reading (`| head`): silence further writes, exit like a SIGPIPE'd tool
os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
code = ExitCode.SIGPIPE
except SimurgError as e:
reporter.error(e.code, e.message + (f"\n hint: {e.hint}" if e.hint else ""), exc=e, file=e.path)
code = e.exit_code
+1
View File
@@ -23,6 +23,7 @@ class ExitCode(IntEnum):
PARTIAL = 3 # >= 1 sample failed and >= 1 succeeded
ALL_FAILED = 4 # run completed but no sample succeeded
SIGINT = 130
SIGPIPE = 141 # the reader of stdout went away (e.g. `simurg inspect … | head`); nothing is reported
SIGTERM = 143