From 34a4578c9e4fb23a8c7e85fe4abd97d897cf8884 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Tue, 16 Jun 2026 11:39:43 +0800 Subject: [PATCH 01/11] fix(wta): log process teardown + agent-pane disconnect; trim log spam Helper/master deaths were undiagnosable: on a non-graceful teardown the process vanished mid-stream and the non-blocking appender's buffered tail was lost (shutdown_flush never ran), while the C++ pane side stayed silent. The success path was logged exhaustively; the teardown path was not. - logging: install a SetConsoleCtrlHandler that logs which control event (CTRL_CLOSE/LOGOFF/SHUTDOWN/BREAK) tore the process down and drains the appender so final records (incl. the transport-lost WARN) reach disk. Returns FALSE -- termination behavior unchanged. Wired into main() so it covers both helper and master. (A hard taskkill /F sends no control event and stays untraceable by design.) - Tab.cpp: _agentPaneLog when the agent pane's connection closes, so a dead helper is recorded on the C++ side and correlates to the millisecond with the Rust logs -- fires even when the master is gone and cannot emit restart_agent_pane. - trim per-token-chunk / per-VT-sequence logs (master forwarding x2, acp session_notification, autofix WtEvent/classified/cross-tab-drop) from debug to trace; keep one per-event breadcrumb at debug and all connection/lifecycle logs intact. cargo +stable check passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/cascadia/TerminalApp/Tab.cpp | 17 +++++++ tools/wta/Cargo.toml | 1 + tools/wta/src/app.rs | 12 +++-- tools/wta/src/logging.rs | 72 ++++++++++++++++++++++++++++ tools/wta/src/main.rs | 5 ++ tools/wta/src/master/mod.rs | 8 +++- tools/wta/src/protocol/acp/client.rs | 4 +- 7 files changed, 113 insertions(+), 6 deletions(-) diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 087c6af03..060b1a2ea 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -6,6 +6,7 @@ #include "Tab.h" #include "AgentPaneContent.h" #include "AgentPaneDragStash.h" +#include "AgentPaneLog.h" #include "SettingsPaneContent.h" #include "Tab.g.cpp" #include "Utils.h" @@ -1423,6 +1424,22 @@ namespace winrt::TerminalApp::implementation }); _tabStatus.IsConnectionClosed(isClosed); + + // When the closed connection is the agent pane's, the wta helper + // backend has died (process exit / ConPTY torn down). The C++ side + // used to be silent here, so a dead helper left no trace in + // terminal-agent-pane.log and "helper stopped responding" incidents + // were undiagnosable from this side. Record it so the UI half of the + // disconnect correlates (to the millisecond) with the Rust logs. + // This fires even when the master itself is gone and so can't emit + // restart_agent_pane. + if (isClosed) + { + if (const auto agentPane = FindAgentPane(); agentPane && agentPane->IsConnectionClosed()) + { + _agentPaneLog("agent pane connection closed — wta helper backend disconnected"); + } + } } } diff --git a/tools/wta/Cargo.toml b/tools/wta/Cargo.toml index f9d296897..a081b4640 100644 --- a/tools/wta/Cargo.toml +++ b/tools/wta/Cargo.toml @@ -27,6 +27,7 @@ windows-sys = { version = "0.61", features = [ "Win32_Foundation", "Win32_Globalization", "Win32_Storage_Packaging_Appx", + "Win32_System_Console", "Win32_System_Environment", "Win32_System_Registry", "Win32_System_Threading", diff --git a/tools/wta/src/app.rs b/tools/wta/src/app.rs index d1ca195fa..367b86e0d 100644 --- a/tools/wta/src/app.rs +++ b/tools/wta/src/app.rs @@ -5137,7 +5137,10 @@ impl App { tab_id, params, } => { - tracing::debug!(target: "autofix", method = %method, pane_id = %pane_id, tab_id = ?tab_id, self_pane_id = ?self.pane_id, "WtEvent"); + // Per-WT-event (every vt_sequence included) — trace-only; the + // single per-event breadcrumb stays at debug in main.rs + // (`wt_event_rx: received event`). + tracing::trace!(target: "autofix", method = %method, pane_id = %pane_id, tab_id = ?tab_id, self_pane_id = ?self.pane_id, "WtEvent"); // Hook bridge events: fire-and-forget into the agent registry // so the agent session view stays current. Unrelated to autofix / @@ -5781,7 +5784,8 @@ impl App { } let notification = classify_wt_event(&method, &pane_id, tab_id.as_deref(), ¶ms); - tracing::debug!(target: "autofix", severity = ?notification.severity, summary = %notification.summary, tab_id = ?notification.tab_id, "classified"); + // Per-WT-event classification — trace-only (vt_sequence volume). + tracing::trace!(target: "autofix", severity = ?notification.severity, summary = %notification.summary, tab_id = ?notification.tab_id, "classified"); // Per-tab filter. WT broadcasts pane-scoped events to every // helper in the window, but another tab's failures are not @@ -5796,7 +5800,9 @@ impl App { && !self_tab.is_empty() && event_tab != self_tab { - tracing::debug!( + // Per-cross-tab-event (very high volume in multi-tab + // windows) — trace-only. + tracing::trace!( target: "autofix", event_tab, self_tab, diff --git a/tools/wta/src/logging.rs b/tools/wta/src/logging.rs index 06c725f90..a2449e26a 100644 --- a/tools/wta/src/logging.rs +++ b/tools/wta/src/logging.rs @@ -175,6 +175,78 @@ pub fn shutdown_flush() { } } +/// Install a Windows console control handler that records the teardown +/// signal and drains the log appender before the OS terminates us. +/// +/// WTA helper/master processes run as ConPTY children of Windows Terminal. +/// When a pane/tab/window closes — or the user logs off / shuts down — the +/// OS delivers a control event (`CTRL_CLOSE`/`CTRL_LOGOFF`/`CTRL_SHUTDOWN`) +/// to those children and then terminates them at the end of a short grace +/// window. Without a handler those deaths are invisible: the process +/// vanishes mid-stream and the non-blocking appender's last buffered +/// records are lost, because [`shutdown_flush`] never runs (the +/// `WorkerGuard` lives in a `static` and `static`s don't `Drop` at +/// teardown). That is exactly the "helper just stopped responding" +/// signature where the success path is logged exhaustively but the +/// teardown path is silent and the incident is undiagnosable. +/// +/// This closes that gap: it logs WHICH control event tore the process down +/// and flushes so the final records (e.g. the transport-lost WARN in +/// `run_acp_client_over_pipe`) actually reach disk. The handler returns +/// FALSE so the default handler still runs and the process terminates as +/// before — we only ADD a log line + flush, we never change termination +/// behavior. +/// +/// Limitation: a hard `TerminateProcess` (Task Manager "End task", +/// `taskkill /F`, an OS resource kill) delivers NO control event and stays +/// untraceable from inside the process — nothing in-process can observe it. +/// Note also that while the Ratatui TUI holds the console in raw mode, +/// Ctrl+C is delivered as a key event (not `CTRL_C_EVENT`), so this handler +/// does not normally see it and does not alter the TUI's Ctrl+C behavior. +pub fn install_ctrl_handler() { + use windows_sys::Win32::System::Console::{ + SetConsoleCtrlHandler, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_C_EVENT, + CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT, + }; + + // Returns `windows_sys`' `BOOL` (an `i32` alias) to match the + // PHANDLER_ROUTINE signature: 0 == FALSE (fall through to default). + unsafe extern "system" fn handler(ctrl_type: u32) -> i32 { + let event = match ctrl_type { + CTRL_C_EVENT => "CTRL_C", + CTRL_BREAK_EVENT => "CTRL_BREAK", + CTRL_CLOSE_EVENT => "CTRL_CLOSE", + CTRL_LOGOFF_EVENT => "CTRL_LOGOFF", + CTRL_SHUTDOWN_EVENT => "CTRL_SHUTDOWN", + _ => "UNKNOWN", + }; + tracing::warn!( + target: "lifecycle", + ctrl_type, + event, + "console control event received — process being torn down; flushing logs" + ); + // Drain the appender so the line above (and any earlier buffered + // records) hit disk before the grace window ends and we're killed. + shutdown_flush(); + // FALSE → fall through to the default handler (terminate). We only + // add logging + flush; termination behavior is unchanged. + 0 + } + + // SAFETY: `handler` is a valid `extern "system"` routine matching the + // PHANDLER_ROUTINE signature; registering a control handler is a + // process-global, thread-safe Win32 operation. + unsafe { + if SetConsoleCtrlHandler(Some(handler), 1) == 0 { + tracing::debug!( + target: "lifecycle", + "SetConsoleCtrlHandler failed — teardown signals will not be logged" + ); + } + } +} + /// Filesystem upkeep run once per process at logging init, before our own /// appender opens. /// diff --git a/tools/wta/src/main.rs b/tools/wta/src/main.rs index 39d127e31..c669dd04e 100644 --- a/tools/wta/src/main.rs +++ b/tools/wta/src/main.rs @@ -590,6 +590,11 @@ async fn main() -> Result<()> { // is held in a global and flushed via `logging::shutdown_flush()` on every // exit path (see the calls below and before each `process::exit`). logging::init(&process_label(&cli)); + // Log + flush on console teardown signals (tab/window close, logoff, + // shutdown) so a torn-down helper/master isn't a silent disappearance — + // see `install_ctrl_handler` for why the success path alone left these + // deaths undiagnosable. + logging::install_ctrl_handler(); tracing::info!(version = env!("CARGO_PKG_VERSION"), "=== wta starting ==="); let locale = cli diff --git a/tools/wta/src/master/mod.rs b/tools/wta/src/master/mod.rs index 263af26df..6a8829b56 100644 --- a/tools/wta/src/master/mod.rs +++ b/tools/wta/src/master/mod.rs @@ -383,7 +383,9 @@ impl acp::Client for MasterClient { "helper notification channel drained — backpressure cleared" ); } - tracing::debug!( + // Per-streamed-chunk; trace-only so default debug logs + // stay readable. Turn-level flow is in `prompt_timing`. + tracing::trace!( target: "master", step = "agent→helper", op = "session_notification", @@ -1886,7 +1888,9 @@ async fn serve_helper( Some(notif) = notif_rx.recv() => { let sid = notif.session_id.clone(); let kind = notification_kind(¬if); - tracing::debug!( + // Per-streamed-chunk; trace-only to keep default debug logs + // readable (this line alone dominated the master log volume). + tracing::trace!( target: "master", step = "master→helper", op = "session_notification", diff --git a/tools/wta/src/protocol/acp/client.rs b/tools/wta/src/protocol/acp/client.rs index 894c9b6a8..292efee4b 100644 --- a/tools/wta/src/protocol/acp/client.rs +++ b/tools/wta/src/protocol/acp/client.rs @@ -1731,7 +1731,9 @@ impl acp::Client for WtaClient { async fn session_notification(&self, args: acp::SessionNotification) -> acp::Result<()> { let kind = session_update_kind(&args.update); - acp_log(&format!("session_notification: kind={}", kind)); + // Per-streamed-chunk; trace-only (not via acp_log's debug) so default + // debug logs aren't flooded with one line per token chunk. + tracing::trace!(target: "acp", "session_notification: kind={}", kind); // The full update carries agent message/thought text, tool-call // content, plan bodies, and replayed user-message chunks — trace only. acp_trace_content(&format!("session_notification update: {:?}", args.update)); From 115f33ffe7bcf3bc2ae742040b2467a25478f4c3 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Tue, 16 Jun 2026 15:01:28 +0800 Subject: [PATCH 02/11] =?UTF-8?q?fix(wta):=20address=20PR=20review=20?= =?UTF-8?q?=E2=80=94=20edge-trigger=20C++=20log,=20warn=20on=20handler=20f?= =?UTF-8?q?ail,=20spell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tab.cpp: edge-trigger the agent-pane disconnect log on the open→closed transition (new `_agentPaneConnectionClosed` guard on Tab). The method runs on every pane connection-state change and on focus changes, so the unconditional log would spam terminal-agent-pane.log after a helper dies. - logging.rs: SetConsoleCtrlHandler install failure now logs at warn, not debug, so release (info) logs explain why teardown signals are missing rather than leaving it silent — the whole point is diagnosability. - spelling: allow the Win32 token PHANDLER (PHANDLER_ROUTINE) in apis.txt. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/actions/spelling/allow/apis.txt | 1 + src/cascadia/TerminalApp/Tab.cpp | 16 +++++++++++----- src/cascadia/TerminalApp/Tab.h | 5 +++++ tools/wta/src/logging.rs | 5 ++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index c151eda1b..cb5918f9b 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -125,6 +125,7 @@ overridable PAGESCROLL PALLOC PATINVERT +PHANDLER PICKFOLDERS PINPUT pmr diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 060b1a2ea..83c10ccdf 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -1433,13 +1433,19 @@ namespace winrt::TerminalApp::implementation // disconnect correlates (to the millisecond) with the Rust logs. // This fires even when the master itself is gone and so can't emit // restart_agent_pane. - if (isClosed) + // + // Edge-triggered: this method runs on every pane's connection-state + // change and on focus changes, so log only on the open→closed + // transition (the `_agentPaneConnectionClosed` guard re-arms when + // the agent pane reconnects or is removed) to avoid spamming the log + // after the helper dies. + const auto agentPane = FindAgentPane(); + const bool agentClosed = agentPane && agentPane->IsConnectionClosed(); + if (agentClosed && !_agentPaneConnectionClosed) { - if (const auto agentPane = FindAgentPane(); agentPane && agentPane->IsConnectionClosed()) - { - _agentPaneLog("agent pane connection closed — wta helper backend disconnected"); - } + _agentPaneLog("agent pane connection closed — wta helper backend disconnected"); } + _agentPaneConnectionClosed = agentClosed; } } diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 05140c6b2..60605680b 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -253,6 +253,11 @@ namespace winrt::TerminalApp::implementation bool _receivedKeyDown{ false }; bool _iconHidden{ false }; bool _changingActivePane{ false }; + // Edge-trigger guard for the agent-pane disconnect log: + // `_UpdateConnectionClosedState` runs on every pane's connection-state + // change (and on focus changes), so we only log on the false→true + // transition. Re-arms when the agent pane reconnects or is recreated. + bool _agentPaneConnectionClosed{ false }; winrt::hstring _stableId{}; diff --git a/tools/wta/src/logging.rs b/tools/wta/src/logging.rs index a2449e26a..c7ea151ef 100644 --- a/tools/wta/src/logging.rs +++ b/tools/wta/src/logging.rs @@ -239,7 +239,10 @@ pub fn install_ctrl_handler() { // process-global, thread-safe Win32 operation. unsafe { if SetConsoleCtrlHandler(Some(handler), 1) == 0 { - tracing::debug!( + // warn (not debug): this is the diagnosability feature failing to + // arm, so release logs (info) must explain why later teardown + // signals are absent rather than leaving it a silent mystery. + tracing::warn!( target: "lifecycle", "SetConsoleCtrlHandler failed — teardown signals will not be logged" ); From b0d2be3b681b367e09186da5b900b69428fdc962 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Tue, 16 Jun 2026 17:30:23 +0800 Subject: [PATCH 03/11] fix(wta): reword comment to clear check-spelling (diagnosability) The noun "diagnosability" isn't in cspell's base dictionary (though "diagnosable" is); reword the one occurrence to "diagnostic" to keep the spell-check gate green without a dictionary entry. Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/wta/src/logging.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/wta/src/logging.rs b/tools/wta/src/logging.rs index c7ea151ef..928342f13 100644 --- a/tools/wta/src/logging.rs +++ b/tools/wta/src/logging.rs @@ -239,8 +239,8 @@ pub fn install_ctrl_handler() { // process-global, thread-safe Win32 operation. unsafe { if SetConsoleCtrlHandler(Some(handler), 1) == 0 { - // warn (not debug): this is the diagnosability feature failing to - // arm, so release logs (info) must explain why later teardown + // warn (not debug): this is the diagnostic feature itself failing + // to arm, so release logs (info) must explain why later teardown // signals are absent rather than leaving it a silent mystery. tracing::warn!( target: "lifecycle", From 6a8226af57e30bcb6649739ec591d19b1f6cde02 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Tue, 16 Jun 2026 19:42:37 +0800 Subject: [PATCH 04/11] fix(wta): include Win32 error code when ctrl-handler install fails Per PR review: SetConsoleCtrlHandler failure now logs GetLastError() in an `error_code` field, captured immediately before any other call can reset thread-last-error, so the warning distinguishes likely causes (no console attached vs. permission failure) instead of just stating it failed. Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/wta/src/logging.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/wta/src/logging.rs b/tools/wta/src/logging.rs index 928342f13..4f98718a5 100644 --- a/tools/wta/src/logging.rs +++ b/tools/wta/src/logging.rs @@ -204,6 +204,7 @@ pub fn shutdown_flush() { /// Ctrl+C is delivered as a key event (not `CTRL_C_EVENT`), so this handler /// does not normally see it and does not alter the TUI's Ctrl+C behavior. pub fn install_ctrl_handler() { + use windows_sys::Win32::Foundation::GetLastError; use windows_sys::Win32::System::Console::{ SetConsoleCtrlHandler, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_C_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT, @@ -239,11 +240,17 @@ pub fn install_ctrl_handler() { // process-global, thread-safe Win32 operation. unsafe { if SetConsoleCtrlHandler(Some(handler), 1) == 0 { + // Capture the Win32 error immediately, before any other call (incl. + // the logging macro's own work) can reset thread-last-error. + let error_code = GetLastError(); // warn (not debug): this is the diagnostic feature itself failing // to arm, so release logs (info) must explain why later teardown - // signals are absent rather than leaving it a silent mystery. + // signals are absent rather than leaving it a silent mystery. The + // error code distinguishes the likely causes (e.g. no console + // attached vs. a permission failure). tracing::warn!( target: "lifecycle", + error_code, "SetConsoleCtrlHandler failed — teardown signals will not be logged" ); } From e3d1c2bbded71fc1f473d07c552104225ea50c79 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Tue, 16 Jun 2026 22:52:56 +0800 Subject: [PATCH 05/11] perf(wta): demote 5 high-volume per-event info logs to debug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These five info! sites fired per-event, not per-lifecycle, and dominated the info logs (~2284 lines in a sample run). Info should be once-per- lifecycle / rare connection events; per-event detail belongs at debug (still available via WTA_LOG=debug). No information removed. - agent_sessions.rs "alive snapshot upgraded Historical row → Live" (1724x) - master/mod.rs "received helper session hook" (157x) - master/mod.rs "handling intellterm.wta/session_hook locally" (157x) - app.rs (agent_route) "applied" (127x) and "routing" (119x) Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/wta/src/agent_sessions.rs | 4 +++- tools/wta/src/app.rs | 6 ++++-- tools/wta/src/master/mod.rs | 8 ++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tools/wta/src/agent_sessions.rs b/tools/wta/src/agent_sessions.rs index 024b4a863..99c723292 100644 --- a/tools/wta/src/agent_sessions.rs +++ b/tools/wta/src/agent_sessions.rs @@ -1083,7 +1083,9 @@ impl AgentSessionRegistry { self.known_alive_panes.insert(pane_lc); } self.dirty = true; - tracing::info!( + // Per-row, fires on every alive-snapshot upgrade — debug, + // not info (this was by far the highest-volume info line). + tracing::debug!( target: "agent_session_registry", key = %sid, pane = ?pane_opt, diff --git a/tools/wta/src/app.rs b/tools/wta/src/app.rs index 367b86e0d..1b7f3b2f3 100644 --- a/tools/wta/src/app.rs +++ b/tools/wta/src/app.rs @@ -579,7 +579,8 @@ where } } let key_for_refresh = key.clone(); - tracing::info!( + // Per-agent-event — debug, not info. + tracing::debug!( target: "agent_route", event = %event, asid = %asid, @@ -764,7 +765,8 @@ where } let dirty = reg.take_dirty(); - tracing::info!( + // Per-agent-event (partner of "routing") — debug, not info. + tracing::debug!( target: "agent_route", event = %event, dirty = dirty, diff --git a/tools/wta/src/master/mod.rs b/tools/wta/src/master/mod.rs index 6a8829b56..856a6db67 100644 --- a/tools/wta/src/master/mod.rs +++ b/tools/wta/src/master/mod.rs @@ -1228,7 +1228,9 @@ impl acp::Agent for HelperHandler { return handle_sessions_list(&self.state, &args.params).await; } if method == crate::session_registry::INTELLTERM_METHOD_SESSION_HOOK { - tracing::info!( + // Per-session-hook (every tool start/stop/session event) — debug, + // not info; the reducer logs its own outcome where it matters. + tracing::debug!( target: "master", op = "ext_method", method = %method, @@ -2170,7 +2172,9 @@ async fn handle_session_hook( acp::Error::invalid_params().data(serde_json::json!({ "message": err.to_string() })) })?; - tracing::info!( + // Per-session-hook event — debug, not info (paired with the master-side + // "handling …session_hook locally" line; together they dominated info). + tracing::debug!( target: "session_hook", event = ?event, "received helper session hook" From 9f42c57a25aaaa706ec84e4808e16a352466ad1f Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Tue, 16 Jun 2026 23:08:15 +0800 Subject: [PATCH 06/11] fix(wta): keep session lifecycle hooks at info, route only routine ones to debug Refines the earlier wholesale demotion of "received helper session hook": terminal/lifecycle transitions (SessionStarted/SessionStopped/PaneClosed/ ConnectionFailed) stay at info so field diagnosis of session-state bugs works from the default release log, while the high-frequency routine events (ToolStarting/ToolCompleted/Notification/resume bookkeeping) go to debug. The generic master-side dispatch breadcrumb stays at debug (it carries no event detail). Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/wta/src/master/mod.rs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/tools/wta/src/master/mod.rs b/tools/wta/src/master/mod.rs index 856a6db67..52a8c0bbd 100644 --- a/tools/wta/src/master/mod.rs +++ b/tools/wta/src/master/mod.rs @@ -2172,13 +2172,27 @@ async fn handle_session_hook( acp::Error::invalid_params().data(serde_json::json!({ "message": err.to_string() })) })?; - // Per-session-hook event — debug, not info (paired with the master-side - // "handling …session_hook locally" line; together they dominated info). - tracing::debug!( - target: "session_hook", - event = ?event, - "received helper session hook" - ); + // Split by event kind so field diagnosis of session-state bugs survives at + // the default release level: terminal/lifecycle transitions (session + // start/stop, pane closed, connection failed) stay at info; the + // high-frequency routine events (tool start/stop, notifications, resume + // bookkeeping) go to debug. Keeps the load-bearing transitions visible + // without the per-tool flood that dominated the info logs. + { + use crate::agent_sessions::SessionEvent; + let lifecycle = matches!( + event, + SessionEvent::SessionStarted { .. } + | SessionEvent::SessionStopped { .. } + | SessionEvent::ConnectionFailed { .. } + | SessionEvent::PaneClosed { .. } + ); + if lifecycle { + tracing::info!(target: "session_hook", event = ?event, "received helper session hook"); + } else { + tracing::debug!(target: "session_hook", event = ?event, "received helper session hook"); + } + } // Capture the session key BEFORE moving `event` into the reducer so // we can dispatch the post-apply title refresh against the right From 671a58b80a7608b4834e809b8e0b5906a6772877 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Tue, 16 Jun 2026 23:24:47 +0800 Subject: [PATCH 07/11] =?UTF-8?q?docs(wta):=20correct=20ctrl-handler=20doc?= =?UTF-8?q?=20=E2=80=94=20master=20is=20job-killed,=20not=20ConPTY?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per PR review: only the helper is a ConPTY child of Windows Terminal and receives CTRL_CLOSE on pane/tab/window close. The master is spawned CREATE_NO_WINDOW and contained in a Job Object with KILL_ON_JOB_CLOSE (C++ SharedWta), so its routine teardown is a job reap — like TerminateProcess, no control event — and this handler does not make it traceable. Tighten the doc comment and the main.rs call-site comment to say so instead of implying both processes always observe these events. No behavior change (comment-only). Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/wta/src/logging.rs | 54 +++++++++++++++++++++++----------------- tools/wta/src/main.rs | 9 ++++--- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/tools/wta/src/logging.rs b/tools/wta/src/logging.rs index 4f98718a5..c9297823f 100644 --- a/tools/wta/src/logging.rs +++ b/tools/wta/src/logging.rs @@ -178,31 +178,39 @@ pub fn shutdown_flush() { /// Install a Windows console control handler that records the teardown /// signal and drains the log appender before the OS terminates us. /// -/// WTA helper/master processes run as ConPTY children of Windows Terminal. -/// When a pane/tab/window closes — or the user logs off / shuts down — the -/// OS delivers a control event (`CTRL_CLOSE`/`CTRL_LOGOFF`/`CTRL_SHUTDOWN`) -/// to those children and then terminates them at the end of a short grace -/// window. Without a handler those deaths are invisible: the process -/// vanishes mid-stream and the non-blocking appender's last buffered -/// records are lost, because [`shutdown_flush`] never runs (the -/// `WorkerGuard` lives in a `static` and `static`s don't `Drop` at -/// teardown). That is exactly the "helper just stopped responding" -/// signature where the success path is logged exhaustively but the -/// teardown path is silent and the incident is undiagnosable. +/// The wta-**helper** runs as a ConPTY child of Windows Terminal (it's the +/// process rendered in the agent pane). When its pane/tab/window closes — or +/// the user logs off / shuts down — the OS delivers a control event +/// (`CTRL_CLOSE`/`CTRL_LOGOFF`/`CTRL_SHUTDOWN`) and then terminates it at the +/// end of a short grace window. Without a handler those deaths are invisible: +/// the process vanishes mid-stream and the non-blocking appender's last +/// buffered records are lost, because [`shutdown_flush`] never runs (the +/// `WorkerGuard` lives in a `static` and `static`s don't `Drop` at teardown). +/// That is exactly the "helper just stopped responding" signature where the +/// success path is logged exhaustively but the teardown path is silent and +/// the incident is undiagnosable. /// -/// This closes that gap: it logs WHICH control event tore the process down -/// and flushes so the final records (e.g. the transport-lost WARN in -/// `run_acp_client_over_pipe`) actually reach disk. The handler returns -/// FALSE so the default handler still runs and the process terminates as -/// before — we only ADD a log line + flush, we never change termination -/// behavior. +/// This closes that gap for the helper: it logs WHICH control event tore the +/// process down and flushes so the final records (e.g. the transport-lost +/// WARN in `run_acp_client_over_pipe`) reach disk. The handler returns FALSE +/// so the default handler still runs and the process terminates as before — +/// we only ADD a log line + flush, never changing termination behavior. It's +/// installed process-wide (cheap and harmless), so any wta process that does +/// receive a console control event benefits. /// -/// Limitation: a hard `TerminateProcess` (Task Manager "End task", -/// `taskkill /F`, an OS resource kill) delivers NO control event and stays -/// untraceable from inside the process — nothing in-process can observe it. -/// Note also that while the Ratatui TUI holds the console in raw mode, -/// Ctrl+C is delivered as a key event (not `CTRL_C_EVENT`), so this handler -/// does not normally see it and does not alter the TUI's Ctrl+C behavior. +/// Coverage limits — what this does NOT catch: +/// * The wta-**master** is spawned `CREATE_NO_WINDOW` and contained in a +/// Job Object with `KILL_ON_JOB_CLOSE` (see C++ `SharedWta`). Its normal +/// teardown is the parent dropping that job, which reaps the master like +/// a `TerminateProcess` — NO control event — so this handler does NOT make +/// routine master teardown traceable. It fires for the master only on +/// genuine console signals (logoff/shutdown), if delivered at all. +/// * A hard `TerminateProcess` (Task Manager "End task", `taskkill /F`, an +/// OS resource kill, or the Job-Object reap above) delivers no control +/// event and stays untraceable from inside the process. +/// * While the Ratatui TUI holds the console in raw mode, Ctrl+C arrives as +/// a key event (not `CTRL_C_EVENT`), so this handler doesn't normally see +/// it and doesn't alter the TUI's Ctrl+C behavior. pub fn install_ctrl_handler() { use windows_sys::Win32::Foundation::GetLastError; use windows_sys::Win32::System::Console::{ diff --git a/tools/wta/src/main.rs b/tools/wta/src/main.rs index c669dd04e..2d04b86d0 100644 --- a/tools/wta/src/main.rs +++ b/tools/wta/src/main.rs @@ -590,10 +590,11 @@ async fn main() -> Result<()> { // is held in a global and flushed via `logging::shutdown_flush()` on every // exit path (see the calls below and before each `process::exit`). logging::init(&process_label(&cli)); - // Log + flush on console teardown signals (tab/window close, logoff, - // shutdown) so a torn-down helper/master isn't a silent disappearance — - // see `install_ctrl_handler` for why the success path alone left these - // deaths undiagnosable. + // Log + flush on console teardown signals (pane/tab/window close, logoff, + // shutdown) so a torn-down helper isn't a silent disappearance. Installed + // process-wide; see `install_ctrl_handler` for coverage limits — notably + // the master is job-killed (KILL_ON_JOB_CLOSE) and won't observe these, so + // its routine teardown stays a known blind spot. logging::install_ctrl_handler(); tracing::info!(version = env!("CARGO_PKG_VERSION"), "=== wta starting ==="); From a92ca638844800413ff5c25134d739cd0bdf44d9 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Wed, 17 Jun 2026 00:08:38 +0800 Subject: [PATCH 08/11] feat(wta): close remaining teardown blind spots (panics + master death) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fills the two diagnosability gaps left after the console-ctrl-handler: 1. Rust panic hook (logging.rs): panics wrote only to stderr — invisible for a ConPTY helper / CREATE_NO_WINDOW master — and the appender's buffered tail was lost on a fatal panic. install_panic_hook() logs the panic (message + location + thread) AND synchronously appends to wta-panic.log (independent of the async appender, so it survives a fatal panic), then chains the previous hook. Does NOT call shutdown_flush (that would kill logging after a recoverable catch_unwind). 2. C++ master-death observability (SharedWta.cpp): the master is job-killed (KILL_ON_JOB_CLOSE) and can't log its own hard death. Log from the parent that observes it: - _OnProcessExited: the wait callback fires for unexpected master exit (crash/OOM/taskkill /F) — the external observer that makes silent master deaths diagnosable. Deliberate teardowns never reach it. - _CleanupLocked: record the deliberate KILL_ON_JOB_CLOSE teardown. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/cascadia/TerminalApp/SharedWta.cpp | 11 ++++ tools/wta/src/logging.rs | 70 ++++++++++++++++++++++++++ tools/wta/src/main.rs | 4 ++ 3 files changed, 85 insertions(+) diff --git a/src/cascadia/TerminalApp/SharedWta.cpp b/src/cascadia/TerminalApp/SharedWta.cpp index 9438c046e..5db663292 100644 --- a/src/cascadia/TerminalApp/SharedWta.cpp +++ b/src/cascadia/TerminalApp/SharedWta.cpp @@ -9,6 +9,7 @@ #include "../WinRTUtils/inc/WtExeUtils.h" #include "../inc/WtaProcess.h" +#include "AgentPaneLog.h" namespace winrt::TerminalApp::implementation { @@ -375,6 +376,9 @@ namespace winrt::TerminalApp::implementation // Order matters: drop the job FIRST so KILL_ON_JOB_CLOSE // terminates wta + descendants while we still hold a process // handle that lets us observe the termination if needed. + // Deliberate teardown: the master is reaped silently (job close, no + // console event), so it can't log its own death — record it here. + _agentPaneLog("releasing wta-master pid=" + std::to_string(_pid) + " (deliberate teardown via KILL_ON_JOB_CLOSE)"); _job.reset(); _process.reset(); if (_waitHandle) @@ -426,6 +430,13 @@ namespace winrt::TerminalApp::implementation // ran. Nothing to do. return; } + // The master exited on its own — crash, OOM, or an external kill + // (taskkill /F, Task Manager). It can't log its own hard death from + // inside, but this wait callback (the parent observing it) can. This + // is the external observer that makes otherwise-silent master deaths + // diagnosable; deliberate teardowns never reach here (they reset + // _process first, so the validity check above bails). + _agentPaneLog("wta-master exited unexpectedly pid=" + std::to_string(observedPid) + " (crash/OOM/external kill — observed by wait callback)"); _job.reset(); _process.reset(); if (_waitHandle) diff --git a/tools/wta/src/logging.rs b/tools/wta/src/logging.rs index c9297823f..35ab50987 100644 --- a/tools/wta/src/logging.rs +++ b/tools/wta/src/logging.rs @@ -265,6 +265,76 @@ pub fn install_ctrl_handler() { } } +/// Install a panic hook that records the panic to disk, then chains to the +/// previous hook. +/// +/// A Rust panic otherwise writes only to stderr — invisible for a ConPTY- +/// hosted helper or a `CREATE_NO_WINDOW` master — and the non-blocking +/// appender's buffered tail is lost when a *fatal* panic kills the process +/// before the background worker drains it. So a panic is a "died for no +/// logged reason" blind spot. This closes it WITHOUT changing panic semantics +/// (it chains the previous hook, so unwind/abort and backtraces are +/// unchanged): +/// * a `tracing::error!` so the panic correlates in the normal log (this +/// drains fine for a *recovered* panic, e.g. behind a `catch_unwind`), and +/// * a synchronous append to `wta-panic.log`, independent of the async +/// appender, so the record reaches disk even when a fatal panic kills us. +/// +/// It deliberately does NOT call [`shutdown_flush`]: that drops the appender +/// guard and would permanently kill logging after a recoverable panic. The +/// synchronous file write is the durable path instead. +pub fn install_panic_hook() { + let prev = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + // Same payload extraction the rest of the codebase uses. + let msg = info + .payload() + .downcast_ref::<&'static str>() + .copied() + .or_else(|| info.payload().downcast_ref::().map(|s| s.as_str())) + .unwrap_or(""); + let location = info + .location() + .map(|l| format!("{}:{}:{}", l.file(), l.line(), l.column())) + .unwrap_or_else(|| "".to_string()); + let thread_name = std::thread::current() + .name() + .unwrap_or("") + .to_string(); + + tracing::error!( + target: "panic", + message = %msg, + location = %location, + thread = %thread_name, + "thread panicked" + ); + + // Guaranteed-on-disk backstop: a fatal main-thread panic unwinds past + // main() without reaching any `shutdown_flush`, so the appender's + // buffered tail (incl. the error above) can be lost. A synchronous + // append here does not depend on the appender being alive. + let millis = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_millis()) + .unwrap_or(0); + if let Ok(mut f) = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(log_dir().join("wta-panic.log")) + { + use std::io::Write; + let _ = writeln!( + f, + "[{millis}ms] pid={} thread={thread_name} panicked at {location}: {msg}", + std::process::id() + ); + } + + prev(info); + })); +} + /// Filesystem upkeep run once per process at logging init, before our own /// appender opens. /// diff --git a/tools/wta/src/main.rs b/tools/wta/src/main.rs index 2d04b86d0..7871da49a 100644 --- a/tools/wta/src/main.rs +++ b/tools/wta/src/main.rs @@ -596,6 +596,10 @@ async fn main() -> Result<()> { // the master is job-killed (KILL_ON_JOB_CLOSE) and won't observe these, so // its routine teardown stays a known blind spot. logging::install_ctrl_handler(); + // Record panics to disk (+ a synchronous wta-panic.log backstop) so a + // panic isn't a silent death — stderr is invisible for a ConPTY helper / + // CREATE_NO_WINDOW master. Chains the default hook; semantics unchanged. + logging::install_panic_hook(); tracing::info!(version = env!("CARGO_PKG_VERSION"), "=== wta starting ==="); let locale = cli From b83af5c8e28c2fd94256aaae443644de93276818 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Wed, 17 Jun 2026 07:39:11 +0800 Subject: [PATCH 09/11] fix(wta): address PR review on blind-spot commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - logging.rs: treat SetConsoleCtrlHandler's ERROR_INVALID_HANDLE (no console, expected for the CREATE_NO_WINDOW master / detached CLI) as debug; keep warn only for unexpected failures, so the warn can't reintroduce startup spam. - master/mod.rs: match the session-hook level decision on &event (borrow, not consume) — clearer intent; event is reused by the reducer below. - Tab.cpp: only walk the tree for the agent pane when something is closed, avoiding a second traversal on the common all-healthy path (still re-arms the edge-trigger guard correctly when nothing is closed). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/cascadia/TerminalApp/Tab.cpp | 13 ++++++++++-- tools/wta/src/logging.rs | 34 +++++++++++++++++++++----------- tools/wta/src/master/mod.rs | 4 +++- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 83c10ccdf..905a0523e 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -1439,8 +1439,17 @@ namespace winrt::TerminalApp::implementation // transition (the `_agentPaneConnectionClosed` guard re-arms when // the agent pane reconnects or is removed) to avoid spamming the log // after the helper dies. - const auto agentPane = FindAgentPane(); - const bool agentClosed = agentPane && agentPane->IsConnectionClosed(); + // + // Only search for the agent pane when SOMETHING is closed: on the + // common all-healthy path no pane is closed, so the agent pane can't + // be either — skip the extra FindAgentPane() tree walk. agentClosed + // staying false there also re-arms the guard. + bool agentClosed = false; + if (isClosed) + { + const auto agentPane = FindAgentPane(); + agentClosed = agentPane && agentPane->IsConnectionClosed(); + } if (agentClosed && !_agentPaneConnectionClosed) { _agentPaneLog("agent pane connection closed — wta helper backend disconnected"); diff --git a/tools/wta/src/logging.rs b/tools/wta/src/logging.rs index 35ab50987..dc25c9d0e 100644 --- a/tools/wta/src/logging.rs +++ b/tools/wta/src/logging.rs @@ -212,7 +212,7 @@ pub fn shutdown_flush() { /// a key event (not `CTRL_C_EVENT`), so this handler doesn't normally see /// it and doesn't alter the TUI's Ctrl+C behavior. pub fn install_ctrl_handler() { - use windows_sys::Win32::Foundation::GetLastError; + use windows_sys::Win32::Foundation::{GetLastError, ERROR_INVALID_HANDLE}; use windows_sys::Win32::System::Console::{ SetConsoleCtrlHandler, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_C_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT, @@ -251,16 +251,28 @@ pub fn install_ctrl_handler() { // Capture the Win32 error immediately, before any other call (incl. // the logging macro's own work) can reset thread-last-error. let error_code = GetLastError(); - // warn (not debug): this is the diagnostic feature itself failing - // to arm, so release logs (info) must explain why later teardown - // signals are absent rather than leaving it a silent mystery. The - // error code distinguishes the likely causes (e.g. no console - // attached vs. a permission failure). - tracing::warn!( - target: "lifecycle", - error_code, - "SetConsoleCtrlHandler failed — teardown signals will not be logged" - ); + if error_code == ERROR_INVALID_HANDLE { + // Expected for a windowless wta process (the CREATE_NO_WINDOW + // master, a detached CLI invocation): there's no console to + // signal, and teardown for those is covered elsewhere (the C++ + // side observes the master via its wait callback). Benign — + // debug only, so it never spams release logs. + tracing::debug!( + target: "lifecycle", + error_code, + "SetConsoleCtrlHandler: no console attached (expected for windowless process)" + ); + } else { + // Any other failure is the diagnostic feature itself failing to + // arm where we DID expect a console (e.g. the helper) — warn so + // release (info) logs explain why later teardown signals are + // absent rather than leaving it a silent mystery. + tracing::warn!( + target: "lifecycle", + error_code, + "SetConsoleCtrlHandler failed — teardown signals will not be logged" + ); + } } } } diff --git a/tools/wta/src/master/mod.rs b/tools/wta/src/master/mod.rs index 52a8c0bbd..dc5aeceac 100644 --- a/tools/wta/src/master/mod.rs +++ b/tools/wta/src/master/mod.rs @@ -2180,8 +2180,10 @@ async fn handle_session_hook( // without the per-tool flood that dominated the info logs. { use crate::agent_sessions::SessionEvent; + // Match on a reference so the level decision borrows rather than + // consumes `event` (it's used again below for the reducer). let lifecycle = matches!( - event, + &event, SessionEvent::SessionStarted { .. } | SessionEvent::SessionStopped { .. } | SessionEvent::ConnectionFailed { .. } From 1d03ff5e076a81516e75fc842aa2c5451f04e977 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Tue, 30 Jun 2026 17:16:39 +0800 Subject: [PATCH 10/11] fix(wta): remove unreachable agent-pane disconnect log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Tab::_UpdateConnectionClosedState disconnect log was dead code — killing a wta-helper never transitions the agent pane's ConptyConnection to Closed, so the IsConnectionClosed() guard was never true. Verified live across helper-up/down kill scenarios. Master teardown logging (SharedWta) and the helper console-control-handler remain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/cascadia/TerminalApp/Tab.cpp | 32 -------------------------------- src/cascadia/TerminalApp/Tab.h | 5 ----- 2 files changed, 37 deletions(-) diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 905a0523e..087c6af03 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -6,7 +6,6 @@ #include "Tab.h" #include "AgentPaneContent.h" #include "AgentPaneDragStash.h" -#include "AgentPaneLog.h" #include "SettingsPaneContent.h" #include "Tab.g.cpp" #include "Utils.h" @@ -1424,37 +1423,6 @@ namespace winrt::TerminalApp::implementation }); _tabStatus.IsConnectionClosed(isClosed); - - // When the closed connection is the agent pane's, the wta helper - // backend has died (process exit / ConPTY torn down). The C++ side - // used to be silent here, so a dead helper left no trace in - // terminal-agent-pane.log and "helper stopped responding" incidents - // were undiagnosable from this side. Record it so the UI half of the - // disconnect correlates (to the millisecond) with the Rust logs. - // This fires even when the master itself is gone and so can't emit - // restart_agent_pane. - // - // Edge-triggered: this method runs on every pane's connection-state - // change and on focus changes, so log only on the open→closed - // transition (the `_agentPaneConnectionClosed` guard re-arms when - // the agent pane reconnects or is removed) to avoid spamming the log - // after the helper dies. - // - // Only search for the agent pane when SOMETHING is closed: on the - // common all-healthy path no pane is closed, so the agent pane can't - // be either — skip the extra FindAgentPane() tree walk. agentClosed - // staying false there also re-arms the guard. - bool agentClosed = false; - if (isClosed) - { - const auto agentPane = FindAgentPane(); - agentClosed = agentPane && agentPane->IsConnectionClosed(); - } - if (agentClosed && !_agentPaneConnectionClosed) - { - _agentPaneLog("agent pane connection closed — wta helper backend disconnected"); - } - _agentPaneConnectionClosed = agentClosed; } } diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 60605680b..05140c6b2 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -253,11 +253,6 @@ namespace winrt::TerminalApp::implementation bool _receivedKeyDown{ false }; bool _iconHidden{ false }; bool _changingActivePane{ false }; - // Edge-trigger guard for the agent-pane disconnect log: - // `_UpdateConnectionClosedState` runs on every pane's connection-state - // change (and on focus changes), so we only log on the false→true - // transition. Re-arms when the agent pane reconnects or is recreated. - bool _agentPaneConnectionClosed{ false }; winrt::hstring _stableId{}; From 10c248262d782be0cbd191da5b25ef238318b1f3 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Tue, 30 Jun 2026 17:24:48 +0800 Subject: [PATCH 11/11] docs(wta): clarify master teardown is logged by SharedWta, not the ctrl handler The install_ctrl_handler comments said routine master teardown "stays a known blind spot", which misleads readers into thinking it is unlogged overall. Scope the claim to this handler and point to SharedWta's parent-side logging (terminal-agent-pane.log) which does record both the deliberate job-close and an unexpected exit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/wta/src/logging.rs | 9 ++++++--- tools/wta/src/main.rs | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/wta/src/logging.rs b/tools/wta/src/logging.rs index dc25c9d0e..4688d04a7 100644 --- a/tools/wta/src/logging.rs +++ b/tools/wta/src/logging.rs @@ -202,9 +202,12 @@ pub fn shutdown_flush() { /// * The wta-**master** is spawned `CREATE_NO_WINDOW` and contained in a /// Job Object with `KILL_ON_JOB_CLOSE` (see C++ `SharedWta`). Its normal /// teardown is the parent dropping that job, which reaps the master like -/// a `TerminateProcess` — NO control event — so this handler does NOT make -/// routine master teardown traceable. It fires for the master only on -/// genuine console signals (logoff/shutdown), if delivered at all. +/// a `TerminateProcess` — NO control event — so *this handler* does not +/// trace routine master teardown. That teardown is not unlogged overall, +/// though: the C++ parent (`SharedWta`) records both the deliberate +/// job-close and an unexpected exit to `terminal-agent-pane.log`. This +/// handler fires for the master only on genuine console signals +/// (logoff/shutdown), if delivered at all. /// * A hard `TerminateProcess` (Task Manager "End task", `taskkill /F`, an /// OS resource kill, or the Job-Object reap above) delivers no control /// event and stays untraceable from inside the process. diff --git a/tools/wta/src/main.rs b/tools/wta/src/main.rs index 3b913aaa2..0a19e6de6 100644 --- a/tools/wta/src/main.rs +++ b/tools/wta/src/main.rs @@ -650,7 +650,9 @@ async fn main() -> Result<()> { // shutdown) so a torn-down helper isn't a silent disappearance. Installed // process-wide; see `install_ctrl_handler` for coverage limits — notably // the master is job-killed (KILL_ON_JOB_CLOSE) and won't observe these, so - // its routine teardown stays a known blind spot. + // *this handler* doesn't trace routine master teardown. That teardown is + // still logged, just by the C++ parent: `SharedWta` records both the + // deliberate job-close and an unexpected exit to terminal-agent-pane.log. logging::install_ctrl_handler(); // Record panics to disk (+ a synchronous wta-panic.log backstop) so a // panic isn't a silent death — stderr is invisible for a ConPTY helper /