fix(oneshot): run memory-provider teardown on -z to stop SIGABRT (exit 134) at shutdown - #48664
fix(oneshot): run memory-provider teardown on -z to stop SIGABRT (exit 134) at shutdown#48664hermes-tmw wants to merge 1 commit into
-z to stop SIGABRT (exit 134) at shutdown#48664Conversation
…t 134) The hermes -z oneshot path bypassed cli.py's session-end finalizer, so it never ran shutdown_memory_provider(). Honcho's fire-and-forget daemon threads (prefetch/sync/dialectic/mem-write) could still be mid-httpx when Py_FinalizeEx ran at interpreter exit, which CPython turns into 'Fatal Python error: Aborted' (SIGABRT / exit 134) — after the response and memory write had already completed. Fix: (1) _run_agent runs shutdown_memory_provider() after agent.chat() to flush the session and join tracked threads; (2) _hard_exit() flushes stdio/logging and calls os._exit(), bypassing Py_FinalizeEx so untracked straggler daemon threads can't trigger the abort (mirrors cli.py's worker-exit pattern, guarded by a SIGALRM deadman). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Duplicate of #37635 — both add the same fix in hermes_cli/oneshot.py: call agent.shutdown_memory_provider() after agent.chat() in the -z oneshot path to quiesce Honcho's untracked daemon threads before Py_FinalizeEx, eliminating the SIGABRT (exit 134) at shutdown. #37635 is the earlier still-open PR with this mechanism. This PR additionally hardens exit via an os._exit-based _hard_exit() (bypassing interpreter finalization), but that's a superset at the same site rather than a distinct fix. Related: #33543 (broader interpreter-finalization fix in hermes_cli/main.py — different mechanism), #43055 (the -z SIGABRT issue). |
|
Duplicate of #37635 — closing in favor of the earlier PR. #37635 already adds Thanks @alt-glitch for catching this. |
Problem
Running a one-shot prompt with
hermes -zexits with 134 (SIGABRT, "Fatal Python error: Aborted") during interpreter shutdown when a memory provider that spawns background daemon threads is active (reproduced with the Honcho provider). The model response and the memory write both complete successfully first — the abort happens only as the process tears down, so it's cosmetic but produces a nonzero exit code and an alarming traceback.Root cause
The
-zoneshot path (hermes_cli/oneshot.py→_run_agent) bypassescli.pyentirely and returns right afteragent.chat(), so it never runs the session-end teardown that the interactive /-qpaths run via_finalize_single_query/shutdown_memory_provider. The Honcho provider spawns several fire-and-forget daemon threads (prefetch / sync / dialectic / mem-write) that make blocking httpx calls. With no teardown, one can still be mid-request whenPy_FinalizeExruns; CPython routes the live daemon thread throughPyThread_exit_thread→pthread_exit, which glibc turns into the fatal abort.Native backtrace (from
coredumpctl), identical across crashes:Confirmed by toggling the provider:
enabled: false→ exit 0; re-enabling → exit 134. Switching Honcho'swriteFrequencyasync→session was not sufficient (other untracked daemon threads remain), so this needs a code fix, not config.Fix
hermes_cli/oneshot.py:_run_agentnow callsagent.shutdown_memory_provider(...)afteragent.chat()— the session-end teardown the oneshot path was missing — so providers flush their session and join their tracked threads (matching the interactive path). Best-effort: a failure here never masks the response already produced._hard_exit(code)helper replaces the three post-agentreturns inrun_oneshot: it flushes logging + stdio (guarded by a SIGALRM deadman) then callsos._exit(code), bypassingPy_FinalizeExso any untracked straggler daemon thread can't trigger the abort. This mirrors the existingos._exitworker-exit pattern already incli.py. The early--provider/--toolsetsvalidationreturns (which run before any agent/threads exist) are left as plain returns.Testing
hermes -p <profile> -z "Reply with exactly: ok"→ printsok, exit 0, no core dump. Reproduced cleanly across repeated runs, including the worst case withwriteFrequency: async(async-writer daemon thread present).Happy to adjust the approach — e.g. if you'd prefer to quiesce/join the provider's untracked daemon threads inside
shutdown_memory_providerrather than hard-exit. Theos._exitapproach is the minimal, low-risk fix given the response + memory write are already complete at that point.🤖 Generated with Claude Code