From ffcb990d331cb47909d44783a5a83955f12ea69f Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Wed, 15 Jul 2026 21:48:45 +0800 Subject: [PATCH] fix(agent-hooks): skip stdin read when it is a tty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The agent-hook emitter reads its JSON payload from stdin until EOF, which assumes the spawner pipes a payload and closes the stream (as Claude Code's hooks do). OpenCode's plugin runner spawns the emitter with stdin inherited from the pane's terminal instead: a tty never reaches EOF, so the read blocked forever, hanging the plugin's async init and leaving OpenCode's TUI blank on startup — while also swallowing the user's keystrokes. Skip the stdin read when stdin is a terminal; a bare event without session_id/message is already a supported payload. Fixes #88 --- src/core/agent_hooks.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/core/agent_hooks.rs b/src/core/agent_hooks.rs index 0aba072d..93bf79cc 100644 --- a/src/core/agent_hooks.rs +++ b/src/core/agent_hooks.rs @@ -15,7 +15,7 @@ //! shell tty7 spawns), so hooks installed globally stay silent when an agent //! runs in another terminal. -use std::io::Read as _; +use std::io::{IsTerminal as _, Read as _}; use std::path::{Path, PathBuf}; use crate::core::cli_agent::AGENT_EVENT_SENTINEL; @@ -40,9 +40,14 @@ pub fn run_agent_hook(agent: &str, event: &str) { } // Hook payload: the agent writes JSON ({"session_id": …, "message": …, …}) // and closes stdin. Absent/malformed input still emits the bare event — - // the state machine works without ids or messages. + // the state machine works without ids or messages. A tty stdin means the + // spawner inherited the pane's terminal instead of piping a payload (e.g. + // OpenCode's plugin runner, issue #88); reading it would block forever on + // an EOF that never comes and swallow the user's keystrokes, so skip it. let mut input = String::new(); - let _ = std::io::stdin().take(MAX_STDIN).read_to_string(&mut input); + if !std::io::stdin().is_terminal() { + let _ = std::io::stdin().take(MAX_STDIN).read_to_string(&mut input); + } let Some(event) = effective_event(agent, event, &input) else { return; };