fix(oneshot): don't swallow stderr on failure; enable faulthandler - #29024
Closed
fabiosiqueira wants to merge 1 commit into
Closed
fix(oneshot): don't swallow stderr on failure; enable faulthandler#29024fabiosiqueira wants to merge 1 commit into
fabiosiqueira wants to merge 1 commit into
Conversation
`run_oneshot` was redirecting both stdout and stderr to /dev/null for the entire call tree to keep the success path silent. The cost is that any failure inside the agent — a SIGSEGV in a native extension, an unhandled Python exception, or a silent empty-response — left the operator with zero signal. This change keeps the silent-on-success behavior but stops discarding the diagnostic on failure: 1. `faulthandler.enable(file=sys.stderr, all_threads=True)` is wired before the redirect block. `contextlib.redirect_stderr` only swaps `sys.stderr` (Python-level); faulthandler holds the underlying fd, so a C-level crash trace still reaches the operator's terminal. (Note: Rust PyO3 panics that call libc `abort()` cannot be caught by faulthandler — those still die mute. This catches everything else: numpy aborts, ctypes mis-calls, double-frees, etc.) 2. Redirected stderr now goes to a `SpooledTemporaryFile` instead of /dev/null. On any exception escaping `_run_agent`, the tail of that buffer is written to the real stderr before the exception propagates. On an empty-response return (the agent ran but produced nothing — e.g. provider returned empty content, rate limited and exhausted retries, etc.) the tail is also surfaced with an explicit header so the operator can diagnose. The happy path is unchanged: stdout = final response, stderr = empty.
fabiosiqueira
force-pushed
the
fix/oneshot-stderr-diagnostics
branch
from
May 21, 2026 12:32
3c8d33f to
c73296f
Compare
This was referenced May 22, 2026
fabiosiqueira
added a commit
to fabiosiqueira/hermes-engine
that referenced
this pull request
Jun 6, 2026
…30623 supersedes PR NousResearch#29024 is superseded by upstream NousResearch#30623, which solves the silent-failure core its own way. Returning hermes_cli/oneshot.py to the origin/main version so local/all-fixes carries no orphan diff for it (the branch should only hold commits with an open upstream PR routing them back to origin/main). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
Closing as superseded by #30623, which lands a fix for the same silent-failure problem (agent exceptions/empty responses no longer exit 0 quietly). The remaining delta here — — 🤖 Claude Opus 4.8 |
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
hermes_cli/oneshot.py:run_oneshotredirects both stdout and stderr to/dev/nullfor the entire_run_agentcall tree. This keeps the success path quiet (the original goal) but discards every diagnostic when something goes wrong:sys.stderr, never reaches the operator (the exception object propagates, but anything the agent wrote tostderralong the way is gone).This was concretely felt while debugging #29021 — the only symptom of a
pydantic-corethread segfault washermes -z "ping"returning empty stdout. Adding a singleprintwas the only way to learn the process had been SIGSEGV'd at all.What this PR changes
run_oneshotkeeps the silent-on-success behavior but stops discarding diagnostics on failure:faulthandler.enable(file=sys.stderr, all_threads=True)is wired before the redirect block.contextlib.redirect_stderronly swapssys.stderr(Python-level); faulthandler holds the underlying fd, so a C-level SIGSEGV/SIGABRT stack still reaches the operator's terminal.Caveat: Rust PyO3 panics that call libc
abort()cannot be caught by faulthandler — those still die mute. This catches everything else: numpy aborts, ctypes mis-calls, double-frees, openssl issues, etc.Redirected stderr →
SpooledTemporaryFileinstead of/dev/null. On any exception escaping_run_agent, the tail of that buffer is written to the real stderr before the exception propagates. On an empty-response return, the tail is also surfaced with an explicithermes -z: empty response; stderr tail follows:header.The happy path is unchanged: stdout = final response, stderr = empty.
Test plan
Validated end-to-end in the Hermes venv (Linux x86_64 musl, Python 3.11.15):
hermes -z "responda apenas 'pong'" --model openrouter/owl-alpha --provider openrouter→ stdout=pong, stderr=0 bytes, exit 0. Identical to current behavior._run_agentto write tosys.stderrand then raise. With this PR, the stderr write reaches the operator (instead of being swallowed) and the exception propagates as before.abort()is uncatchable by faulthandler. Acknowledged in commit message; the dep bump in fix(deps): bump pydantic to 2.13.4 to avoid pydantic-core thread segfault #29021 is what fixes that particular crash, not this PR.Platforms tested
Related
Companion to #29021 (pydantic bump). #29021 fixes the specific crash; this PR fixes the diagnosability gap that hid the crash and turned a 30-second triage into a multi-hour investigation.
🤖 Generated with Claude Code