hermes - ✅(Solved) Fix CLI crashes with OSError: [Errno 5] Input/output error on interrupt (signal handling) [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#13710Fetched 2026-04-22 08:04:30
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1

Error Message

OSError: [Errno 5] Input/output error

During handling of the above exception, another exception occurred:

Traceback (most recent call last): File ".../prompt_toolkit/renderer.py", line 429, in reset self.output.flush() File ".../prompt_toolkit/output/vt100.py", line 706, in flush flush_stdout(self.stdout, data) File ".../prompt_toolkit/output/flush_stdout.py", line 37, in flush_stdout stdout.flush() OSError: [Errno 5] Input/output error

Root Cause

Root Cause Analysis

Fix Action

Fixed

PR fix notes

PR #13720: fix(cli): suppress OSError EIO on interrupt shutdown

Description (problem / solution / changelog)

Summary

Fixes #13710 — CLI crashes with OSError: [Errno 5] Input/output error when the user interrupts a long-running task.

Root Cause

When the user sends an interrupt, prompt_toolkit attempts to flush stdout during emergency shutdown. If stdout is in a broken state (redirected to /dev/null, pipe closed, terminal gone), the flush raises OSError: [Errno 5] Input/output error.

The existing _suppress_closed_loop_errors asyncio exception handler suppresses RuntimeError("Event loop is closed") and KeyError("is not registered") but has no branch for OSError with errno.EIO. The error falls through to loop.default_exception_handler(), which crashes the CLI.

The outer except (KeyError, OSError) fallback block also misses errno.EIO — it only catches "is not registered" and "Bad file descriptor" string patterns.

Fix

Two defense layers, consistent with the existing error suppression architecture:

  1. _suppress_closed_loop_errors: Added OSError with errno.EIO check, matching the existing pattern for RuntimeError and KeyError.

  2. Outer except (KeyError, OSError) block: Added errno.EIO check as a new branch before the existing string-match guards. Silently suppresses the error via pass instead of printing a misleading stdin-related message (this is a stdout issue, not stdin).

Changes

FileChange
cli.pyimport errno, two new EIO suppression branches (8 insertions, 3 deletions)
tests/hermes_cli/test_suppress_eio_on_interrupt.py9 regression tests covering both defense layers

Tests

  • 6 tests for _suppress_closed_loop_errors: EIO suppressed, other errno propagates, unrelated exceptions propagate, no-exception context propagates, existing RuntimeError/KeyError suppression preserved.
  • 3 tests for outer except block logic: EIO guard matches, Bad file descriptor matches, other OSError does not match.
  • All 9 tests pass.

Changed files

  • cli.py (modified, +8/-3)
  • tests/hermes_cli/test_suppress_eio_on_interrupt.py (added, +115/-0)

Code Example

OSError: [Errno 5] Input/output error

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".../prompt_toolkit/renderer.py", line 429, in reset
    self.output.flush()
  File ".../prompt_toolkit/output/vt100.py", line 706, in flush
    flush_stdout(self.stdout, data)
  File ".../prompt_toolkit/output/flush_stdout.py", line 37, in flush_stdout
    stdout.flush()
OSError: [Errno 5] Input/output error

---

05:31:21 interrupt fired: msg=..., agent_running=True
05:45:43 ... OSError: [Errno 5] Input/output error (crash + restart)
05:55:42 interrupt fired: msg=..., agent_running=True
06:00:09 ... OSError: [Errno 5] Input/output error (crash + restart)
RAW_BUFFERClick to expand / collapse

Description When the user sends an interrupt message while Hermes CLI is executing a long-running task, the process crashes with OSError: [Errno 5] Input/output error during prompt_toolkit shutdown.

Reproduction

  1. Start a long-running task in Hermes CLI
  2. Send an interrupt message (press Enter with busy_input_mode=interrupt)
  3. Observe: process crashes and restarts

Error Log

OSError: [Errno 5] Input/output error

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".../prompt_toolkit/renderer.py", line 429, in reset
    self.output.flush()
  File ".../prompt_toolkit/output/vt100.py", line 706, in flush
    flush_stdout(self.stdout, data)
  File ".../prompt_toolkit/output/flush_stdout.py", line 37, in flush_stdout
    stdout.flush()
OSError: [Errno 5] Input/output error

Root Cause Analysis

Two issues compound:

  1. Signal handler raises KeyboardInterrupt at arbitrary C stack point (cli.py:9682): The _signal_handler raises KeyboardInterrupt inside a Unix signal handler. This causes prompt_toolkit to attempt emergency shutdown, which tries to flush stdout that is in a broken/devnull state.

  2. OSError with errno 5 not suppressed (cli.py:9699-9706): The _suppress_closed_loop_errors exception handler only suppresses:

    • RuntimeError: "Event loop is closed"
    • KeyError: "is not registered"

    But OSError: [Errno 5] Input/output error from stdout flush falls through to the default handler, which then crashes the process.

Additional Context from interrupt_debug.log

05:31:21 interrupt fired: msg=..., agent_running=True
05:45:43 ... OSError: [Errno 5] Input/output error (crash + restart)
05:55:42 interrupt fired: msg=..., agent_running=True
06:00:09 ... OSError: [Errno 5] Input/output error (crash + restart)

Environment

  • macOS Darwin 25.4.0 (Apple Silicon M3)
  • Python 3.9.6 via uv
  • Hermes Agent latest (NousResearch/hermes-agent)
  • busy_input_mode: interrupt

Expected Behavior Hermes CLI should gracefully handle interrupts without crashing.

Suggested Fix

  1. Add OSError with errno 5 (Input/output error) to _suppress_closed_loop_errors
  2. Alternatively, change signal handler to use a flag-based approach instead of raising an exception

extent analysis

TL;DR

The most likely fix is to add OSError with errno 5 to the _suppress_closed_loop_errors exception handler to prevent the process from crashing when an interrupt message is sent during a long-running task.

Guidance

  • Modify the _suppress_closed_loop_errors exception handler in cli.py to catch and suppress OSError with errno 5, in addition to the existing exceptions.
  • Consider changing the signal handler to use a flag-based approach instead of raising a KeyboardInterrupt exception to avoid interrupting the C stack.
  • Verify that the fix works by reproducing the issue and checking that the process no longer crashes when an interrupt message is sent.
  • Review the interrupt_debug.log to ensure that the OSError with errno 5 is no longer occurring after applying the fix.

Example

def _suppress_closed_loop_errors(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except (RuntimeError, KeyError):
            # existing exceptions
            pass
        except OSError as e:
            if e.errno == 5:  # Input/output error
                pass
            else:
                raise
    return wrapper

Notes

The suggested fix assumes that the OSError with errno 5 is the primary cause of the crash. However, the root cause analysis suggests that there may be other issues compounding the problem. Additional debugging and testing may be necessary to ensure that the fix is comprehensive.

Recommendation

Apply the workaround by adding OSError with errno 5 to the _suppress_closed_loop_errors exception handler, as this is a more straightforward and targeted fix compared to changing the signal handler.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING