diff --git a/cmux-tui/crates/cmux-tui-core/src/journal_ingress.rs b/cmux-tui/crates/cmux-tui-core/src/journal_ingress.rs index c96f67ba3c66..abd2986b56d1 100644 --- a/cmux-tui/crates/cmux-tui-core/src/journal_ingress.rs +++ b/cmux-tui/crates/cmux-tui-core/src/journal_ingress.rs @@ -1038,7 +1038,6 @@ fn run(mux: Weak, receivers: JournalIngressReceivers) { }; if Instant::now() >= retry_deadline { stop_writer_after_retry_deadline( - &mux, &receivers, &batch, pending, @@ -1073,7 +1072,6 @@ fn run(mux: Weak, receivers: JournalIngressReceivers) { let remaining = retry_deadline.saturating_duration_since(Instant::now()); if remaining.is_zero() { stop_writer_after_retry_deadline( - &mux, &receivers, &batch, pending, @@ -1107,7 +1105,11 @@ fn run(mux: Weak, receivers: JournalIngressReceivers) { let failure = receivers.state.fail(format!( "session journal writer failed permanently: {summary}" )); - mux.request_daemon_shutdown(); + // A terminal journal failure must not strand the + // live terminal hosts. Mark journaling failed, + // fail pending receipts, and keep the daemon + // available so callers can reconnect or export + // their session before the next restart. #[cfg(test)] receivers.state.notify_failure_for_test(&failure); complete_batch_error(&batch, failure.clone()); @@ -1173,7 +1175,6 @@ fn admit_batch_commit( } fn stop_writer_after_retry_deadline( - mux: &Mux, receivers: &JournalIngressReceivers, batch: &[QueuedJournalEvent], pending: VecDeque>, @@ -1185,7 +1186,9 @@ fn stop_writer_after_retry_deadline( "session journal writer timed out after {} ms: {detail}", JOURNAL_DURABLE_WAIT.as_millis() )); - mux.request_daemon_shutdown(); + // Journal persistence is a recoverability aid, not a reason to tear down + // every live terminal host. The failed state rejects later journal writes + // while the daemon remains available for the user to recover the session. #[cfg(test)] receivers.state.notify_failure_for_test(&failure); complete_batch_error(batch, failure.clone()); @@ -1664,7 +1667,7 @@ mod tests { ); assert!( mux.daemon_shutdown_requested(), - "a journal lock beyond the fixed deadline must stop the daemon" + "explicit shutdown must request daemon shutdown even when journal flush times out" ); blocker.execute_batch("ROLLBACK;").unwrap(); assert!( @@ -1725,8 +1728,8 @@ mod tests { ); failed_receiver.recv_timeout(Duration::from_secs(1)).unwrap(); assert!( - mux.daemon_shutdown_requested(), - "a producer database lock beyond the deadline must stop the daemon" + !mux.daemon_shutdown_requested(), + "a producer database lock must not stop live terminal hosts" ); blocker.execute_batch("ROLLBACK;").unwrap(); drop(blocker); @@ -1781,7 +1784,7 @@ mod tests { "registry mutex admission must not outlive the producer deadline" ); failed_receiver.recv_timeout(Duration::from_secs(1)).unwrap(); - assert!(mux.daemon_shutdown_requested()); + assert!(!mux.daemon_shutdown_requested()); release.send(()).unwrap(); blocker.join().unwrap(); let records = mux.session_journal_after(0, 1024).unwrap().records; @@ -1847,7 +1850,7 @@ mod tests { release.send(()).unwrap(); producer.join().unwrap(); failed_receiver.recv_timeout(Duration::from_secs(1)).unwrap(); - assert!(mux.daemon_shutdown_requested()); + assert!(!mux.daemon_shutdown_requested()); let records = mux.session_journal_after(0, 1024).unwrap().records; assert!( records @@ -2467,8 +2470,8 @@ mod tests { assert!(started.elapsed() < Duration::from_secs(3)); assert!(error.to_string().contains("injected permanent terminal journal failure")); assert!( - mux.daemon_shutdown_requested(), - "a permanent output gap must stop the daemon instead of continuing silently" + !mux.daemon_shutdown_requested(), + "a permanent output gap must not stop live terminal hosts" ); assert!( mux.try_journal_terminal_output( diff --git a/cmux-tui/crates/cmux-tui-core/src/resource_router/content.rs b/cmux-tui/crates/cmux-tui-core/src/resource_router/content.rs index 02a8c50ce445..9f2eb10edb1a 100644 --- a/cmux-tui/crates/cmux-tui-core/src/resource_router/content.rs +++ b/cmux-tui/crates/cmux-tui-core/src/resource_router/content.rs @@ -194,7 +194,17 @@ fn terminal_output_read( let after = optional_decimal(&request.fields, "after")?; // The catalog injects the default and enforces the 1..=4 MiB bounds. let max_bytes = required_u64(&request.fields, "max_bytes")?; - mux.terminal_output_read(&terminal_id, after, max_bytes).map_err(resource_operation_error) + mux.terminal_output_read(&terminal_id, after, max_bytes).map_err(|error| { + // Journal failures can contain SQLite paths, trigger text, and other + // host diagnostics. Keep those details in daemon logs; this resource + // is user-facing and must return a stable, non-sensitive error. + eprintln!("cmux-tui: terminal output read failed for {terminal_id}: {error:#}"); + ResourceError::operation_failed( + "terminal.output_read", + "could not read terminal output", + json!({}), + ) + }) } fn terminal_wait(mux: &Arc, request: &ParsedResourceRequest) -> Result { diff --git a/vendor/bonsplit b/vendor/bonsplit index f33c31cdc087..351bfa7039a2 160000 --- a/vendor/bonsplit +++ b/vendor/bonsplit @@ -1 +1 @@ -Subproject commit f33c31cdc08781257425700cdc070867b14bd98f +Subproject commit 351bfa7039a261715f8ea59f8d28157ee678b024