From 7696ddc59eba81624014d7bfc063f8ad7fe61598 Mon Sep 17 00:00:00 2001 From: ambition0802 <673088860@qq.com> Date: Sat, 2 May 2026 02:05:57 -0700 Subject: [PATCH] fix(cli): robust paste file expansion and process_loop error handling (#17666) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two narrow fixes for long pasted messages silently disappearing: 1. _expand_paste_references: replace path.exists() + read_text() with try/except (OSError, IOError). Closes the TOCTOU window where a paste file deleted between check and read raised FileNotFoundError, bubbled up through process_loop's outer except, and silently dropped the user's input. Failures now return the placeholder text and log a warning. 2. process_loop outer except: logger.warning() instead of print(). prompt_toolkit's TUI swallows stdout, so 'Error: …' was invisible to the user. Logged errors are discoverable via hermes logs. Dropped the larger interrupt_queue→pending_input drain that was part of the original PR — that's a separate class of input-drop (in-progress interrupt handling) unrelated to the paste-file TOCTOU reported in the issue, and worth its own review. Salvage of #17939. --- cli.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cli.py b/cli.py index f0ba6fc9..da917ae1 100644 --- a/cli.py +++ b/cli.py @@ -2928,7 +2928,14 @@ class HermesCLI: def _expand_ref(match): path = Path(match.group(1)) - return path.read_text(encoding="utf-8") if path.exists() else match.group(0) + # Use try/except instead of path.exists() to avoid TOCTOU race: + # the paste file may be deleted between check and read, causing + # the input to be silently dropped (#17666). + try: + return path.read_text(encoding="utf-8") + except (OSError, IOError): + logger.warning("Paste file gone or unreadable, returning placeholder: %s", path) + return match.group(0) return paste_ref_re.sub(_expand_ref, text) @@ -11584,7 +11591,7 @@ class HermesCLI: pass # Non-fatal — don't break the main loop except Exception as e: - print(f"Error: {e}") + logger.warning("process_loop unhandled error (msg may be lost): %s", e) # Start processing thread process_thread = threading.Thread(target=process_loop, daemon=True)