Bug 1 (event_loop.rs): poll(-1) blocked forever when TUI went silent;
startup timer never fired so prompt was never injected. Fixed: poll(50ms)
+ empty-slice tick so poll_timers() runs on every iteration.
Bug 2 (session.rs): read_fd from open_fifo_nonblock() was dropped
immediately after as_raw_fd(), closing the fd the event loop was polling.
Fixed: store both (_fifo_read, _fifo_keeper) to keep both alive.
Bug 3 (pty.rs): child inherited CLAUDE_CODE_SESSION_ID from parent, so
it wrote events into the parent transcript and skipped Stop hook dispatch.
Fixed: unsetenv(CLAUDE_CODE_SESSION_ID) in child after fork; preserve
CLAUDECODE=1 and CLAUDE_CODE_ENTRYPOINT=cli.
Bug 4 (session.rs): empty-slice timer ticks were fed to startup.feed()
which reset last_output_at, preventing idle timer from ever firing.
Fixed: guard startup.feed() and terminal.feed() from empty slices.
Bug 5 (session.rs): handle.join() blocked main thread for up to
cli.timeout (default 3600s) on any early exit, because the timeout thread
sleeps for the full duration. Also, waitpid blocked forever if child
ignored SIGTERM. Fixed: drop(timeout_thread) to detach; add kill_child()
helper (SIGTERM → 2s wait → SIGKILL) used on all cleanup paths.
All five confirmed fixed: claude-print "what is 2+2?" → "4", exit 0,
cc_entrypoint=cli in session JSONL (subscription billing verified).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>