From 9fa16d7d49b7f26abbcb45839d2ab71761f11a2c Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Wed, 10 Jun 2026 10:00:54 +0800 Subject: [PATCH 01/12] Keep delegate cwd path out of the default info log run_delegate logged cwd at info! level. cwd is a filesystem path carrying the Windows username and folder names, so it is personal data, yet info is the shipped release default. Move cwd onto the existing trace-only delegate.content channel so nothing personal lands in wta-delegate.log at the default filter; the info line keeps only prompt_chars and the agent command name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/wta/src/main.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/wta/src/main.rs b/tools/wta/src/main.rs index 11920b67c..895359e09 100644 --- a/tools/wta/src/main.rs +++ b/tools/wta/src/main.rs @@ -1616,9 +1616,11 @@ async fn run_delegate( delegate_model: Option<&str>, cwd: Option<&str>, ) -> Result<()> { - // Log the prompt length, not the text — the prompt is user content. - tracing::info!(prompt_chars = prompt.map(|p| p.chars().count()), agent = agent_cmd, cwd, "run_delegate started"); - tracing::trace!(target: "delegate.content", prompt = ?prompt, "run_delegate prompt"); + // Log the prompt length, not the text — the prompt is user content. `cwd` + // is a filesystem path (carries the username / folder names), so it is + // personal data too: keep it on the trace-only content channel. + tracing::info!(prompt_chars = prompt.map(|p| p.chars().count()), agent = agent_cmd, "run_delegate started"); + tracing::trace!(target: "delegate.content", prompt = ?prompt, cwd, "run_delegate prompt"); let (debug_tx, _) = tokio::sync::mpsc::unbounded_channel::(); let channel = match connect_to_wt_protocol(debug_tx).await { From fef9dfa45d8ea23572a2dc7ad02722fe2c2b0c02 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Wed, 10 Jun 2026 10:24:19 +0800 Subject: [PATCH 02/12] Gate remaining default-level cwd logs onto trace content channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Copilot review on PR #261: the delegate path was not the only default-level cwd leak. run_delegate's acp_load_session, app.rs dispatch_resume / dispatch_resume_in_agent_pane (agents_view) and the inbound load_session handler, plus master's new_session, all emitted the full cwd path at info!/warn! — which the shipped info filter writes. Each default-level line now logs has_cwd (a bool) instead of the path; the full cwd moves to a sibling trace! on the matching *.content target (delegate.content / acp_load_session.content / agents_view.content / master.content), consistent with the repo convention that user/agent content only lands at trace. debug!-level cwd logs (coordinator_log, acp_log_built_prompt) are left as-is since debug is dev-only and never the shipped default. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/wta/src/app.rs | 39 ++++++++++++++++++++++++++++++++----- tools/wta/src/main.rs | 14 +++++++++++-- tools/wta/src/master/mod.rs | 9 ++++++++- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/tools/wta/src/app.rs b/tools/wta/src/app.rs index 8ba57796c..2f87f2968 100644 --- a/tools/wta/src/app.rs +++ b/tools/wta/src/app.rs @@ -3131,9 +3131,14 @@ impl App { tracing::warn!( target: "agents_view", key = %key, - cwd = %raw_cwd_string, "dispatch_resume: stored cwd is no longer a valid directory; falling back to profile default", ); + tracing::trace!( + target: "agents_view.content", + key = %key, + cwd = %raw_cwd_string, + "dispatch_resume: invalid stored cwd", + ); } let short_key: String = key.chars().take(8).collect(); let launch_commandline = format!( @@ -3186,9 +3191,15 @@ impl App { cli = %cli_id, commandline = %commandline, launch_commandline = %launch_commandline, - cwd = %cwd_string, + has_cwd = !cwd_string.is_empty(), "dispatch_resume: new-tab scheduled", ); + tracing::trace!( + target: "agents_view.content", + key = %key, + cwd = %cwd_string, + "dispatch_resume: new-tab cwd", + ); #[cfg(test)] { @@ -3331,9 +3342,14 @@ impl App { tracing::warn!( target: "agents_view", key = %key, - cwd = %raw_cwd_string, "dispatch_resume_in_agent_pane: stored cwd is no longer a valid directory; omitting from resume_in_new_agent_tab event", ); + tracing::trace!( + target: "agents_view.content", + key = %key, + cwd = %raw_cwd_string, + "dispatch_resume_in_agent_pane: invalid stored cwd", + ); } let cwd_string = valid_cwd.unwrap_or_default(); @@ -3359,9 +3375,15 @@ impl App { tracing::info!( target: "agents_view", key = %s.key, - cwd = %cwd_string, + has_cwd = !cwd_string.is_empty(), "dispatch_resume_in_agent_pane: resume_in_new_agent_tab event published", ); + tracing::trace!( + target: "agents_view.content", + key = %s.key, + cwd = %cwd_string, + "dispatch_resume_in_agent_pane: resume cwd", + ); #[cfg(test)] { @@ -5423,9 +5445,16 @@ impl App { target: "acp_load_session", tab_id, session_id, - cwd = ?cwd, + has_cwd = cwd.is_some(), "inbound load_session event from WT" ); + tracing::trace!( + target: "acp_load_session.content", + tab_id, + session_id, + cwd = ?cwd, + "inbound load_session cwd" + ); if tab_id.is_empty() || session_id.is_empty() { tracing::warn!( target: "acp_load_session", diff --git a/tools/wta/src/main.rs b/tools/wta/src/main.rs index 895359e09..a66e9ea48 100644 --- a/tools/wta/src/main.rs +++ b/tools/wta/src/main.rs @@ -2569,9 +2569,13 @@ async fn run_acp_app( if v.is_none() { tracing::warn!( target: "acp_load_session", - cwd = %s, "--initial-load-cwd refers to a missing directory; dropping from load_session params", ); + tracing::trace!( + target: "acp_load_session.content", + cwd = %s, + "--initial-load-cwd missing directory", + ); } v }); @@ -2579,9 +2583,15 @@ async fn run_acp_app( target: "acp_load_session", session_id = sid, tab_id = %tab_id, - cwd = ?cwd, + has_cwd = cwd.is_some(), "queueing boot-time initial load_session via AppEvent::WtEvent" ); + tracing::trace!( + target: "acp_load_session.content", + session_id = sid, + cwd = ?cwd, + "initial load_session cwd" + ); let mut params = serde_json::Map::new(); params.insert( "tab_id".to_string(), diff --git a/tools/wta/src/master/mod.rs b/tools/wta/src/master/mod.rs index 9483079e0..bc9ecaca9 100644 --- a/tools/wta/src/master/mod.rs +++ b/tools/wta/src/master/mod.rs @@ -775,11 +775,18 @@ impl acp::Agent for HelperHandler { step = "helper→agent", op = "new_session", helper_id = ?self.helper_id, - cwd = ?args.cwd, + has_cwd = !args.cwd.as_os_str().is_empty(), mcp_servers = args.mcp_servers.len(), pane_session_id = ?wta_meta.pane_session_id, "forwarding new_session" ); + tracing::trace!( + target: "master.content", + step = "helper→agent", + op = "new_session", + cwd = ?args.cwd, + "new_session cwd" + ); let resp = self.agent_conn.new_session(args).await?; let forwarder = self.forwarder_for_route("new_session")?; // Record routing entry BEFORE returning so the helper can't From ef54b3d911e9a5330bd6ae20c0ebcba5f8ef23d4 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Wed, 10 Jun 2026 11:06:39 +0800 Subject: [PATCH 03/12] Strip PII from remaining info+ WTA logs Full sweep of info/warn/error logs for personal data (the previous commits only covered cwd). Three classes addressed: Tier 1 (user content / arbitrary files): master forwards fs/read|write_text_file and the on-disk session title at info!. The agent-controlled file path and the title (often derived from the user's prompt) now log only length/op at info; the full value moves to the trace-only master.content / session_hook.content channel. Tier 2 (fixed tool/runtime paths whose only PII is the Windows username): hooks-installer config/plugin/marketplace/bundle paths, the master-pipe discovery file, and the agent-pane origin record. New crate::logging::redact_user_path() rewrites the %LOCALAPPDATA%/%APPDATA%/%USERPROFILE% prefix to a placeholder, so these stay debuggable at info/warn without leaking the username. Tier 3 (captured subprocess output): the plugin-CLI stdout/stderr/args move to the agent_hooks.content trace; only exe + exit status stay at default. wtcli stdout/stderr were reviewed and left as-is (tool diagnostics: pane GUIDs / protocol errors, no user content). Out of scope: debug!-level logs (dev-only) and Tier 4 agent command lines (user-configured, not runtime PII). cargo build + logging unit tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/wta/src/agent_hooks_installer.rs | 71 ++++++++++++++++---------- tools/wta/src/agent_pane_origin.rs | 2 +- tools/wta/src/logging.rs | 61 ++++++++++++++++++++++ tools/wta/src/master/mod.rs | 32 +++++++++--- 4 files changed, 130 insertions(+), 36 deletions(-) diff --git a/tools/wta/src/agent_hooks_installer.rs b/tools/wta/src/agent_hooks_installer.rs index 0adf1a130..793aa5611 100644 --- a/tools/wta/src/agent_hooks_installer.rs +++ b/tools/wta/src/agent_hooks_installer.rs @@ -590,7 +590,7 @@ fn install_for_claude(home: &Path) { tracing::warn!( target: "agent_hooks", err = %e, - path = %settings_path.display(), + path = %crate::logging::redact_user_path(&settings_path), "failed to strip legacy wta hooks from settings.json; non-fatal", ); } @@ -742,8 +742,8 @@ fn maybe_stage_bundle_for_codex(source: &Path) -> Option { Ok(()) => { tracing::info!( target: "agent_hooks", - source = %source.display(), - staged = %staged.display(), + source = %crate::logging::redact_user_path(&source), + staged = %crate::logging::redact_user_path(&staged), "restaged codex bundle out of WindowsApps", ); Some(staged) @@ -752,8 +752,8 @@ fn maybe_stage_bundle_for_codex(source: &Path) -> Option { tracing::warn!( target: "agent_hooks", err = %e, - source = %source.display(), - staged = %staged.display(), + source = %crate::logging::redact_user_path(&source), + staged = %crate::logging::redact_user_path(&staged), "failed to restage codex bundle out of WindowsApps; using original path", ); None @@ -803,7 +803,7 @@ fn install_for_copilot(home: &Path) { tracing::warn!( target: "copilot_hooks", err = %e, - path = %settings_path.display(), + path = %crate::logging::redact_user_path(&settings_path), "failed to clean up stale wt-local marketplace entry; non-fatal", ); } @@ -852,13 +852,13 @@ fn install_for_copilot(home: &Path) { tracing::warn!( target: "copilot_hooks", err = %e, - path = %stale.display(), + path = %crate::logging::redact_user_path(&stale), "failed to remove stale _direct folder; non-fatal", ); } else { tracing::info!( target: "copilot_hooks", - path = %stale.display(), + path = %crate::logging::redact_user_path(&stale), "removed stale _direct plugin folder", ); } @@ -2078,19 +2078,29 @@ fn run_plugin_cli_capture_with_env( tracing::warn!( target: "agent_hooks", exe = exe, + status = ?output.status.code(), + "plugin CLI returned non-zero exit", + ); + tracing::trace!( + target: "agent_hooks.content", + exe = exe, args = ?args, stdout = %stdout.trim(), stderr = %stderr.trim(), - status = ?output.status.code(), - "plugin CLI returned non-zero exit", + "plugin CLI non-zero output", ); } else { tracing::info!( target: "agent_hooks", exe = exe, + "plugin CLI succeeded", + ); + tracing::trace!( + target: "agent_hooks.content", + exe = exe, args = ?args, stdout = %stdout.trim(), - "plugin CLI succeeded", + "plugin CLI success output", ); } Ok(CliRunOutcome { @@ -2148,9 +2158,14 @@ fn run_plugin_cli_with_env( tracing::info!( target: "agent_hooks", exe = exe, - args = ?args, "plugin CLI exited non-zero but matched idempotency substring; treating as success", ); + tracing::trace!( + target: "agent_hooks.content", + exe = exe, + args = ?args, + "plugin CLI idempotency-match args", + ); return Ok(()); } return Err(std::io::Error::new( @@ -2231,8 +2246,8 @@ fn maybe_stage_bundle_for_claude(source: &Path) -> Option { Ok(()) => { tracing::info!( target: "agent_hooks", - source = %source.display(), - staged = %staged.display(), + source = %crate::logging::redact_user_path(&source), + staged = %crate::logging::redact_user_path(&staged), "staged claude bundle out of WindowsApps to sidestep Node.js cpSync EPERM", ); Some(staged) @@ -2241,8 +2256,8 @@ fn maybe_stage_bundle_for_claude(source: &Path) -> Option { tracing::warn!( target: "agent_hooks", err = %e, - source = %source.display(), - staged = %staged.display(), + source = %crate::logging::redact_user_path(&source), + staged = %crate::logging::redact_user_path(&staged), "failed to stage claude bundle under LOCALAPPDATA; \ falling back to WindowsApps source (claude plugin install \ may fail with EPERM)", @@ -2313,7 +2328,7 @@ fn cleanup_legacy_claude_hooks(settings_path: &Path) -> std::io::Result<()> { tracing::warn!( target: "agent_hooks", err = %e, - path = %settings_path.display(), + path = %crate::logging::redact_user_path(&settings_path), "settings.json malformed; leaving untouched", ); return Ok(()); @@ -2362,7 +2377,7 @@ fn cleanup_legacy_claude_hooks(settings_path: &Path) -> std::io::Result<()> { fs::write(settings_path, serialized)?; tracing::info!( target: "agent_hooks", - path = %settings_path.display(), + path = %crate::logging::redact_user_path(&settings_path), "stripped legacy wta hooks block", ); Ok(()) @@ -2446,7 +2461,7 @@ fn cleanup_stale_copilot_marketplace( tracing::warn!( target: "copilot_hooks", err = %e, - path = %settings_path.display(), + path = %crate::logging::redact_user_path(&settings_path), "settings.json malformed; leaving untouched", ); return Ok(()); @@ -2500,9 +2515,9 @@ fn cleanup_stale_copilot_marketplace( fs::write(settings_path, serialized)?; tracing::info!( target: "copilot_hooks", - path = %settings_path.display(), - old = %old_path, - new = %expected_str, + path = %crate::logging::redact_user_path(&settings_path), + old = %crate::logging::redact_user_path(&old_path), + new = %crate::logging::redact_user_path(&expected_str), "rewrote stale wt-local marketplace path", ); Ok(()) @@ -3181,7 +3196,7 @@ fn save_upgrade_state(path: &Path, state: &UpgradeState) { tracing::warn!( target: "agent_hooks", err = %e, - path = %parent.display(), + path = %crate::logging::redact_user_path(&parent), "failed to create upgrade-state parent dir", ); return; @@ -3198,7 +3213,7 @@ fn save_upgrade_state(path: &Path, state: &UpgradeState) { tracing::warn!( target: "agent_hooks", err = %e, - path = %path.display(), + path = %crate::logging::redact_user_path(&path), "failed to write upgrade-state file", ); } @@ -3232,7 +3247,7 @@ fn cleanup_stale_claude_marketplace( tracing::warn!( target: "agent_hooks", err = %e, - path = %known_path.display(), + path = %crate::logging::redact_user_path(&known_path), "known_marketplaces.json malformed; leaving untouched", ); return Ok(()); @@ -3290,9 +3305,9 @@ fn cleanup_stale_claude_marketplace( fs::write(known_path, serialized)?; tracing::info!( target: "agent_hooks", - path = %known_path.display(), - old = %old_path, - new = %expected_str, + path = %crate::logging::redact_user_path(&known_path), + old = %crate::logging::redact_user_path(&old_path), + new = %crate::logging::redact_user_path(&expected_str), "rewrote stale wt-local marketplace path (claude)", ); Ok(()) diff --git a/tools/wta/src/agent_pane_origin.rs b/tools/wta/src/agent_pane_origin.rs index 58de8422b..ae18b00f9 100644 --- a/tools/wta/src/agent_pane_origin.rs +++ b/tools/wta/src/agent_pane_origin.rs @@ -96,7 +96,7 @@ pub fn append_default(session_id: &str, pane_session_id: Option<&str>) { tracing::warn!( target: "agent_pane_origin", session_id = %session_id, - path = %path.display(), + path = %crate::logging::redact_user_path(&path), error = %err, "failed to append origin record", ); diff --git a/tools/wta/src/logging.rs b/tools/wta/src/logging.rs index 06c725f90..fb6dc31f2 100644 --- a/tools/wta/src/logging.rs +++ b/tools/wta/src/logging.rs @@ -38,6 +38,43 @@ pub(crate) fn default_filter_directive(debug_assertions: bool) -> &'static str { } } +/// Replace the current user's home / app-data prefixes in a path with stable +/// placeholders, so fixed tool / runtime paths can be logged at default levels +/// without leaking the Windows username. +/// +/// Only the user-profile prefix is personal in these paths (e.g. +/// `C:\Users\\AppData\Local\…\config.json`); the rest is a fixed, +/// non-personal tool layout that stays useful for diagnostics. Use this for +/// *known* tool/runtime paths only — for arbitrary paths the agent reads or +/// writes (which can reveal user file/folder names beyond the prefix), keep +/// the full path on a trace-only `*.content` target instead. +/// +/// Prefixes are tried most-specific first (`LOCALAPPDATA` is itself under +/// `USERPROFILE`) and matched case-insensitively (Windows paths). +pub(crate) fn redact_user_path(path: impl AsRef) -> String { + let s = path.as_ref().to_string_lossy().into_owned(); + for (var, placeholder) in [ + ("LOCALAPPDATA", "%LOCALAPPDATA%"), + ("APPDATA", "%APPDATA%"), + ("USERPROFILE", "%USERPROFILE%"), + ] { + if let Some(prefix) = std::env::var_os(var) { + let prefix = prefix.to_string_lossy(); + if prefix.is_empty() { + continue; + } + // `get(..len)` is None when `len` is not a char boundary, so this + // never panics on a non-ASCII username. + if let Some(head) = s.get(..prefix.len()) { + if head.eq_ignore_ascii_case(&prefix) { + return format!("{placeholder}{}", &s[prefix.len()..]); + } + } + } + } + s +} + /// Root of the WTA log tree: `/logs` (or a temp-dir fallback). fn logs_root() -> std::path::PathBuf { crate::runtime_paths::intelligent_terminal_local_root() @@ -266,6 +303,30 @@ mod tests { assert_eq!(default_filter_directive(false), "info"); } + #[test] + fn redact_user_path_strips_localappdata_prefix() { + let local = std::env::var_os("LOCALAPPDATA"); + if let Some(local) = local { + let local = local.to_string_lossy().into_owned(); + let full = std::path::Path::new(&local) + .join("IntelligentTerminal") + .join("master-pipe.txt"); + let redacted = redact_user_path(&full); + assert!( + redacted.starts_with("%LOCALAPPDATA%"), + "expected placeholder prefix, got: {redacted}", + ); + assert!(!redacted.contains(&local), "raw prefix leaked: {redacted}"); + assert!(redacted.ends_with("master-pipe.txt")); + } + } + + #[test] + fn redact_user_path_leaves_unrelated_paths_untouched() { + let p = std::path::Path::new(r"D:\unrelated\proj\file.rs"); + assert_eq!(redact_user_path(p), r"D:\unrelated\proj\file.rs"); + } + #[test] fn release_default_filter_enables_info() { // The EnvFilter built from the release default must enable info (and diff --git a/tools/wta/src/master/mod.rs b/tools/wta/src/master/mod.rs index bc9ecaca9..94978980c 100644 --- a/tools/wta/src/master/mod.rs +++ b/tools/wta/src/master/mod.rs @@ -492,9 +492,15 @@ impl acp::Client for MasterClient { op = "write_text_file", helper_id = ?helper_id, session_id = ?sid, - path = ?args.path, "forwarding fs/write_text_file to helper" ); + tracing::trace!( + target: "master.content", + op = "write_text_file", + session_id = ?sid, + path = ?args.path, + "fs/write_text_file path" + ); forwarder.write_text_file(args).await } @@ -510,9 +516,15 @@ impl acp::Client for MasterClient { op = "read_text_file", helper_id = ?helper_id, session_id = ?sid, - path = ?args.path, "forwarding fs/read_text_file to helper" ); + tracing::trace!( + target: "master.content", + op = "read_text_file", + session_id = ?sid, + path = ?args.path, + "fs/read_text_file path" + ); forwarder.read_text_file(args).await } @@ -1289,7 +1301,7 @@ impl MasterPipeDiscoveryGuard { if let Err(err) = std::fs::create_dir_all(parent) { tracing::warn!( target: "master", - path = %path.display(), + path = %crate::logging::redact_user_path(&path), error = %err, "failed to create master pipe discovery directory" ); @@ -1302,14 +1314,14 @@ impl MasterPipeDiscoveryGuard { match std::fs::write(path, pipe_name) { Ok(()) => tracing::info!( target: "master", - path = %path.display(), + path = %crate::logging::redact_user_path(&path), pipe_name = %pipe_name, "master pipe discovery file written" ), Err(err) => { tracing::warn!( target: "master", - path = %path.display(), + path = %crate::logging::redact_user_path(&path), error = %err, "failed to write master pipe discovery file" ); @@ -1339,7 +1351,7 @@ impl Drop for MasterPipeDiscoveryGuard { if let Err(err) = std::fs::remove_file(path) { tracing::warn!( target: "master", - path = %path.display(), + path = %crate::logging::redact_user_path(&path), error = %err, "failed to remove master pipe discovery file" ); @@ -2289,9 +2301,15 @@ where tracing::info!( target: "session_hook", session_id = %sid.0, - new_title = %disk_title, + title_len = disk_title.len(), "upgraded synthetic title from on-disk session artefacts", ); + tracing::trace!( + target: "session_hook.content", + session_id = %sid.0, + new_title = %disk_title, + "upgraded synthetic title", + ); } upgraded } From 55bc6654da3df627ca94adb9833caaace9c9ffdc Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Thu, 11 Jun 2026 15:03:49 +0800 Subject: [PATCH 04/12] Remove cwd details from WTA logs Avoid replacing cwd paths with diagnostic has_cwd fields or trace-only cwd logs. Keep the operational log events but omit cwd entirely from logging.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/wta/src/app.rs | 36 ------------------------------------ tools/wta/src/main.rs | 12 ------------ tools/wta/src/master/mod.rs | 8 -------- 3 files changed, 56 deletions(-) diff --git a/tools/wta/src/app.rs b/tools/wta/src/app.rs index 2f87f2968..d1ca195fa 100644 --- a/tools/wta/src/app.rs +++ b/tools/wta/src/app.rs @@ -3133,12 +3133,6 @@ impl App { key = %key, "dispatch_resume: stored cwd is no longer a valid directory; falling back to profile default", ); - tracing::trace!( - target: "agents_view.content", - key = %key, - cwd = %raw_cwd_string, - "dispatch_resume: invalid stored cwd", - ); } let short_key: String = key.chars().take(8).collect(); let launch_commandline = format!( @@ -3154,8 +3148,6 @@ impl App { argv.push("-d".to_string()); argv.push(cwd.clone()); } - let cwd_string = valid_cwd.clone().unwrap_or_default(); - // Optimistic state flip: bump Historical/Ended -> Idle so a rapid // second Enter on the same row sees a non-terminal status and // skips this branch (idempotent: ResumeDispatched no-ops on live @@ -3191,15 +3183,8 @@ impl App { cli = %cli_id, commandline = %commandline, launch_commandline = %launch_commandline, - has_cwd = !cwd_string.is_empty(), "dispatch_resume: new-tab scheduled", ); - tracing::trace!( - target: "agents_view.content", - key = %key, - cwd = %cwd_string, - "dispatch_resume: new-tab cwd", - ); #[cfg(test)] { @@ -3344,12 +3329,6 @@ impl App { key = %key, "dispatch_resume_in_agent_pane: stored cwd is no longer a valid directory; omitting from resume_in_new_agent_tab event", ); - tracing::trace!( - target: "agents_view.content", - key = %key, - cwd = %raw_cwd_string, - "dispatch_resume_in_agent_pane: invalid stored cwd", - ); } let cwd_string = valid_cwd.unwrap_or_default(); @@ -3375,15 +3354,8 @@ impl App { tracing::info!( target: "agents_view", key = %s.key, - has_cwd = !cwd_string.is_empty(), "dispatch_resume_in_agent_pane: resume_in_new_agent_tab event published", ); - tracing::trace!( - target: "agents_view.content", - key = %s.key, - cwd = %cwd_string, - "dispatch_resume_in_agent_pane: resume cwd", - ); #[cfg(test)] { @@ -5445,16 +5417,8 @@ impl App { target: "acp_load_session", tab_id, session_id, - has_cwd = cwd.is_some(), "inbound load_session event from WT" ); - tracing::trace!( - target: "acp_load_session.content", - tab_id, - session_id, - cwd = ?cwd, - "inbound load_session cwd" - ); if tab_id.is_empty() || session_id.is_empty() { tracing::warn!( target: "acp_load_session", diff --git a/tools/wta/src/main.rs b/tools/wta/src/main.rs index a66e9ea48..f28ad6fed 100644 --- a/tools/wta/src/main.rs +++ b/tools/wta/src/main.rs @@ -2571,11 +2571,6 @@ async fn run_acp_app( target: "acp_load_session", "--initial-load-cwd refers to a missing directory; dropping from load_session params", ); - tracing::trace!( - target: "acp_load_session.content", - cwd = %s, - "--initial-load-cwd missing directory", - ); } v }); @@ -2583,15 +2578,8 @@ async fn run_acp_app( target: "acp_load_session", session_id = sid, tab_id = %tab_id, - has_cwd = cwd.is_some(), "queueing boot-time initial load_session via AppEvent::WtEvent" ); - tracing::trace!( - target: "acp_load_session.content", - session_id = sid, - cwd = ?cwd, - "initial load_session cwd" - ); let mut params = serde_json::Map::new(); params.insert( "tab_id".to_string(), diff --git a/tools/wta/src/master/mod.rs b/tools/wta/src/master/mod.rs index ef2d713ae..faef2ac81 100644 --- a/tools/wta/src/master/mod.rs +++ b/tools/wta/src/master/mod.rs @@ -812,18 +812,10 @@ impl acp::Agent for HelperHandler { step = "helper→agent", op = "new_session", helper_id = ?self.helper_id, - has_cwd = !args.cwd.as_os_str().is_empty(), mcp_servers = args.mcp_servers.len(), pane_session_id = ?wta_meta.pane_session_id, "forwarding new_session" ); - tracing::trace!( - target: "master.content", - step = "helper→agent", - op = "new_session", - cwd = ?args.cwd, - "new_session cwd" - ); let resp = self .forward_new_session_to_agent( args, From 1ae3400588bd849f78bc518d6aa3d52f531fb015 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Thu, 11 Jun 2026 15:16:33 +0800 Subject: [PATCH 05/12] Drop WTA path redaction helper Remove redact_user_path and stop logging fixed runtime/tool paths at default levels. Keep the operational log messages without path/source/staged/old/new fields.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/wta/src/agent_hooks_installer.rs | 28 ------------ tools/wta/src/agent_pane_origin.rs | 1 - tools/wta/src/logging.rs | 61 -------------------------- tools/wta/src/master/mod.rs | 4 -- 4 files changed, 94 deletions(-) diff --git a/tools/wta/src/agent_hooks_installer.rs b/tools/wta/src/agent_hooks_installer.rs index 793aa5611..874e50c97 100644 --- a/tools/wta/src/agent_hooks_installer.rs +++ b/tools/wta/src/agent_hooks_installer.rs @@ -590,7 +590,6 @@ fn install_for_claude(home: &Path) { tracing::warn!( target: "agent_hooks", err = %e, - path = %crate::logging::redact_user_path(&settings_path), "failed to strip legacy wta hooks from settings.json; non-fatal", ); } @@ -742,8 +741,6 @@ fn maybe_stage_bundle_for_codex(source: &Path) -> Option { Ok(()) => { tracing::info!( target: "agent_hooks", - source = %crate::logging::redact_user_path(&source), - staged = %crate::logging::redact_user_path(&staged), "restaged codex bundle out of WindowsApps", ); Some(staged) @@ -752,8 +749,6 @@ fn maybe_stage_bundle_for_codex(source: &Path) -> Option { tracing::warn!( target: "agent_hooks", err = %e, - source = %crate::logging::redact_user_path(&source), - staged = %crate::logging::redact_user_path(&staged), "failed to restage codex bundle out of WindowsApps; using original path", ); None @@ -803,7 +798,6 @@ fn install_for_copilot(home: &Path) { tracing::warn!( target: "copilot_hooks", err = %e, - path = %crate::logging::redact_user_path(&settings_path), "failed to clean up stale wt-local marketplace entry; non-fatal", ); } @@ -852,13 +846,11 @@ fn install_for_copilot(home: &Path) { tracing::warn!( target: "copilot_hooks", err = %e, - path = %crate::logging::redact_user_path(&stale), "failed to remove stale _direct folder; non-fatal", ); } else { tracing::info!( target: "copilot_hooks", - path = %crate::logging::redact_user_path(&stale), "removed stale _direct plugin folder", ); } @@ -2246,8 +2238,6 @@ fn maybe_stage_bundle_for_claude(source: &Path) -> Option { Ok(()) => { tracing::info!( target: "agent_hooks", - source = %crate::logging::redact_user_path(&source), - staged = %crate::logging::redact_user_path(&staged), "staged claude bundle out of WindowsApps to sidestep Node.js cpSync EPERM", ); Some(staged) @@ -2256,8 +2246,6 @@ fn maybe_stage_bundle_for_claude(source: &Path) -> Option { tracing::warn!( target: "agent_hooks", err = %e, - source = %crate::logging::redact_user_path(&source), - staged = %crate::logging::redact_user_path(&staged), "failed to stage claude bundle under LOCALAPPDATA; \ falling back to WindowsApps source (claude plugin install \ may fail with EPERM)", @@ -2328,7 +2316,6 @@ fn cleanup_legacy_claude_hooks(settings_path: &Path) -> std::io::Result<()> { tracing::warn!( target: "agent_hooks", err = %e, - path = %crate::logging::redact_user_path(&settings_path), "settings.json malformed; leaving untouched", ); return Ok(()); @@ -2377,7 +2364,6 @@ fn cleanup_legacy_claude_hooks(settings_path: &Path) -> std::io::Result<()> { fs::write(settings_path, serialized)?; tracing::info!( target: "agent_hooks", - path = %crate::logging::redact_user_path(&settings_path), "stripped legacy wta hooks block", ); Ok(()) @@ -2461,7 +2447,6 @@ fn cleanup_stale_copilot_marketplace( tracing::warn!( target: "copilot_hooks", err = %e, - path = %crate::logging::redact_user_path(&settings_path), "settings.json malformed; leaving untouched", ); return Ok(()); @@ -2469,7 +2454,6 @@ fn cleanup_stale_copilot_marketplace( }; let expected_str = expected_source.to_string_lossy().into_owned(); - let old_path: String; { let Some(root) = settings.as_object_mut() else { @@ -2507,7 +2491,6 @@ fn cleanup_stale_copilot_marketplace( } source.insert("path".to_string(), Value::String(expected_str.clone())); - old_path = current; } let serialized = serde_json::to_string_pretty(&settings) @@ -2515,9 +2498,6 @@ fn cleanup_stale_copilot_marketplace( fs::write(settings_path, serialized)?; tracing::info!( target: "copilot_hooks", - path = %crate::logging::redact_user_path(&settings_path), - old = %crate::logging::redact_user_path(&old_path), - new = %crate::logging::redact_user_path(&expected_str), "rewrote stale wt-local marketplace path", ); Ok(()) @@ -3196,7 +3176,6 @@ fn save_upgrade_state(path: &Path, state: &UpgradeState) { tracing::warn!( target: "agent_hooks", err = %e, - path = %crate::logging::redact_user_path(&parent), "failed to create upgrade-state parent dir", ); return; @@ -3213,7 +3192,6 @@ fn save_upgrade_state(path: &Path, state: &UpgradeState) { tracing::warn!( target: "agent_hooks", err = %e, - path = %crate::logging::redact_user_path(&path), "failed to write upgrade-state file", ); } @@ -3247,7 +3225,6 @@ fn cleanup_stale_claude_marketplace( tracing::warn!( target: "agent_hooks", err = %e, - path = %crate::logging::redact_user_path(&known_path), "known_marketplaces.json malformed; leaving untouched", ); return Ok(()); @@ -3256,7 +3233,6 @@ fn cleanup_stale_claude_marketplace( let expected_str = expected_source.to_string_lossy().into_owned(); let mut changed = false; - let mut old_path = String::new(); { let Some(root) = settings.as_object_mut() else { @@ -3275,7 +3251,6 @@ fn cleanup_stale_claude_marketplace( .to_string(); if !paths_equivalent(Path::new(¤t), expected_source) { source.insert("path".to_string(), Value::String(expected_str.clone())); - old_path = current; changed = true; } } @@ -3305,9 +3280,6 @@ fn cleanup_stale_claude_marketplace( fs::write(known_path, serialized)?; tracing::info!( target: "agent_hooks", - path = %crate::logging::redact_user_path(&known_path), - old = %crate::logging::redact_user_path(&old_path), - new = %crate::logging::redact_user_path(&expected_str), "rewrote stale wt-local marketplace path (claude)", ); Ok(()) diff --git a/tools/wta/src/agent_pane_origin.rs b/tools/wta/src/agent_pane_origin.rs index ae18b00f9..b099cdc92 100644 --- a/tools/wta/src/agent_pane_origin.rs +++ b/tools/wta/src/agent_pane_origin.rs @@ -96,7 +96,6 @@ pub fn append_default(session_id: &str, pane_session_id: Option<&str>) { tracing::warn!( target: "agent_pane_origin", session_id = %session_id, - path = %crate::logging::redact_user_path(&path), error = %err, "failed to append origin record", ); diff --git a/tools/wta/src/logging.rs b/tools/wta/src/logging.rs index fb6dc31f2..06c725f90 100644 --- a/tools/wta/src/logging.rs +++ b/tools/wta/src/logging.rs @@ -38,43 +38,6 @@ pub(crate) fn default_filter_directive(debug_assertions: bool) -> &'static str { } } -/// Replace the current user's home / app-data prefixes in a path with stable -/// placeholders, so fixed tool / runtime paths can be logged at default levels -/// without leaking the Windows username. -/// -/// Only the user-profile prefix is personal in these paths (e.g. -/// `C:\Users\\AppData\Local\…\config.json`); the rest is a fixed, -/// non-personal tool layout that stays useful for diagnostics. Use this for -/// *known* tool/runtime paths only — for arbitrary paths the agent reads or -/// writes (which can reveal user file/folder names beyond the prefix), keep -/// the full path on a trace-only `*.content` target instead. -/// -/// Prefixes are tried most-specific first (`LOCALAPPDATA` is itself under -/// `USERPROFILE`) and matched case-insensitively (Windows paths). -pub(crate) fn redact_user_path(path: impl AsRef) -> String { - let s = path.as_ref().to_string_lossy().into_owned(); - for (var, placeholder) in [ - ("LOCALAPPDATA", "%LOCALAPPDATA%"), - ("APPDATA", "%APPDATA%"), - ("USERPROFILE", "%USERPROFILE%"), - ] { - if let Some(prefix) = std::env::var_os(var) { - let prefix = prefix.to_string_lossy(); - if prefix.is_empty() { - continue; - } - // `get(..len)` is None when `len` is not a char boundary, so this - // never panics on a non-ASCII username. - if let Some(head) = s.get(..prefix.len()) { - if head.eq_ignore_ascii_case(&prefix) { - return format!("{placeholder}{}", &s[prefix.len()..]); - } - } - } - } - s -} - /// Root of the WTA log tree: `/logs` (or a temp-dir fallback). fn logs_root() -> std::path::PathBuf { crate::runtime_paths::intelligent_terminal_local_root() @@ -303,30 +266,6 @@ mod tests { assert_eq!(default_filter_directive(false), "info"); } - #[test] - fn redact_user_path_strips_localappdata_prefix() { - let local = std::env::var_os("LOCALAPPDATA"); - if let Some(local) = local { - let local = local.to_string_lossy().into_owned(); - let full = std::path::Path::new(&local) - .join("IntelligentTerminal") - .join("master-pipe.txt"); - let redacted = redact_user_path(&full); - assert!( - redacted.starts_with("%LOCALAPPDATA%"), - "expected placeholder prefix, got: {redacted}", - ); - assert!(!redacted.contains(&local), "raw prefix leaked: {redacted}"); - assert!(redacted.ends_with("master-pipe.txt")); - } - } - - #[test] - fn redact_user_path_leaves_unrelated_paths_untouched() { - let p = std::path::Path::new(r"D:\unrelated\proj\file.rs"); - assert_eq!(redact_user_path(p), r"D:\unrelated\proj\file.rs"); - } - #[test] fn release_default_filter_enables_info() { // The EnvFilter built from the release default must enable info (and diff --git a/tools/wta/src/master/mod.rs b/tools/wta/src/master/mod.rs index faef2ac81..5f7a578ee 100644 --- a/tools/wta/src/master/mod.rs +++ b/tools/wta/src/master/mod.rs @@ -1323,7 +1323,6 @@ impl MasterPipeDiscoveryGuard { if let Err(err) = std::fs::create_dir_all(parent) { tracing::warn!( target: "master", - path = %crate::logging::redact_user_path(&path), error = %err, "failed to create master pipe discovery directory" ); @@ -1336,14 +1335,12 @@ impl MasterPipeDiscoveryGuard { match std::fs::write(path, pipe_name) { Ok(()) => tracing::info!( target: "master", - path = %crate::logging::redact_user_path(&path), pipe_name = %pipe_name, "master pipe discovery file written" ), Err(err) => { tracing::warn!( target: "master", - path = %crate::logging::redact_user_path(&path), error = %err, "failed to write master pipe discovery file" ); @@ -1373,7 +1370,6 @@ impl Drop for MasterPipeDiscoveryGuard { if let Err(err) = std::fs::remove_file(path) { tracing::warn!( target: "master", - path = %crate::logging::redact_user_path(&path), error = %err, "failed to remove master pipe discovery file" ); From 30b8c7363156cc013cea479288713093a1563333 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Thu, 11 Jun 2026 15:18:07 +0800 Subject: [PATCH 06/12] Log session title length as characters Use chars().count() for the title_len field so non-ASCII session titles are not reported as byte lengths.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/wta/src/master/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/wta/src/master/mod.rs b/tools/wta/src/master/mod.rs index 5f7a578ee..b2e5e9b1c 100644 --- a/tools/wta/src/master/mod.rs +++ b/tools/wta/src/master/mod.rs @@ -2319,7 +2319,7 @@ where tracing::info!( target: "session_hook", session_id = %sid.0, - title_len = disk_title.len(), + title_len = disk_title.chars().count(), "upgraded synthetic title from on-disk session artefacts", ); tracing::trace!( From 578ea5627d098f6f047aead5710781de02309e3c Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Thu, 11 Jun 2026 15:22:15 +0800 Subject: [PATCH 07/12] Remove extra trace content logging Stop moving sensitive paths, command lines, titles, and plugin CLI output into trace-only content logs. Keep the operational log events without those extra fields.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/wta/src/agent_hooks_installer.rs | 21 ------------------- tools/wta/src/coordinator.rs | 7 +++---- tools/wta/src/main.rs | 10 ++++----- tools/wta/src/master/mod.rs | 28 -------------------------- tools/wta/src/protocol/acp/client.rs | 4 +--- 5 files changed, 8 insertions(+), 62 deletions(-) diff --git a/tools/wta/src/agent_hooks_installer.rs b/tools/wta/src/agent_hooks_installer.rs index 874e50c97..f1e6cff92 100644 --- a/tools/wta/src/agent_hooks_installer.rs +++ b/tools/wta/src/agent_hooks_installer.rs @@ -2073,27 +2073,12 @@ fn run_plugin_cli_capture_with_env( status = ?output.status.code(), "plugin CLI returned non-zero exit", ); - tracing::trace!( - target: "agent_hooks.content", - exe = exe, - args = ?args, - stdout = %stdout.trim(), - stderr = %stderr.trim(), - "plugin CLI non-zero output", - ); } else { tracing::info!( target: "agent_hooks", exe = exe, "plugin CLI succeeded", ); - tracing::trace!( - target: "agent_hooks.content", - exe = exe, - args = ?args, - stdout = %stdout.trim(), - "plugin CLI success output", - ); } Ok(CliRunOutcome { success: output.status.success(), @@ -2152,12 +2137,6 @@ fn run_plugin_cli_with_env( exe = exe, "plugin CLI exited non-zero but matched idempotency substring; treating as success", ); - tracing::trace!( - target: "agent_hooks.content", - exe = exe, - args = ?args, - "plugin CLI idempotency-match args", - ); return Ok(()); } return Err(std::io::Error::new( diff --git a/tools/wta/src/coordinator.rs b/tools/wta/src/coordinator.rs index e20b4ca6a..f68d86329 100644 --- a/tools/wta/src/coordinator.rs +++ b/tools/wta/src/coordinator.rs @@ -424,11 +424,10 @@ async fn execute_choice( .unwrap_or(DelegatePromptDelivery::LaunchThenSend); let target_label = open_target_label(target); coordinator_log(&format!( - "open_and_send begin target={} parent={:?} agent={:?} cwd={:?} title={:?} direction={:?} delivery_mode={} input_chars={}", + "open_and_send begin target={} parent={:?} agent={:?} title={:?} direction={:?} delivery_mode={} input_chars={}", target_label, parent, agent, - cwd, title, direction, delegate_prompt_delivery_label(delivery_mode), @@ -511,8 +510,8 @@ async fn execute_choice( } => { let target_label = open_target_label(target); coordinator_log(&format!( - "open begin target={} parent={:?} cwd={:?} title={:?} direction={:?}", - target_label, parent, cwd, title, direction + "open begin target={} parent={:?} title={:?} direction={:?}", + target_label, parent, title, direction )); let _ = event_tx.send(AppEvent::ExecutionInfo(format!( "Opening {}.", diff --git a/tools/wta/src/main.rs b/tools/wta/src/main.rs index f28ad6fed..84b45865c 100644 --- a/tools/wta/src/main.rs +++ b/tools/wta/src/main.rs @@ -1616,11 +1616,9 @@ async fn run_delegate( delegate_model: Option<&str>, cwd: Option<&str>, ) -> Result<()> { - // Log the prompt length, not the text — the prompt is user content. `cwd` - // is a filesystem path (carries the username / folder names), so it is - // personal data too: keep it on the trace-only content channel. + // Log the prompt length, not the text — the prompt is user content. tracing::info!(prompt_chars = prompt.map(|p| p.chars().count()), agent = agent_cmd, "run_delegate started"); - tracing::trace!(target: "delegate.content", prompt = ?prompt, cwd, "run_delegate prompt"); + tracing::trace!(target: "delegate.content", prompt = ?prompt, "run_delegate prompt"); let (debug_tx, _) = tokio::sync::mpsc::unbounded_channel::(); let channel = match connect_to_wt_protocol(debug_tx).await { @@ -1720,8 +1718,8 @@ async fn delegate_with_context( // The commandline bakes in the user prompt (`-i ""`); keep it out // of the debug log and only emit it at trace. - tracing::debug!(cwd, "delegate_with_context: launching"); - tracing::trace!(target: "delegate.content", commandline, cwd, "delegate_with_context commandline"); + tracing::debug!("delegate_with_context: launching"); + tracing::trace!(target: "delegate.content", commandline, "delegate_with_context commandline"); shell_mgr .wt_create_tab(Some(&commandline), cwd, None) diff --git a/tools/wta/src/master/mod.rs b/tools/wta/src/master/mod.rs index b2e5e9b1c..7e04474ff 100644 --- a/tools/wta/src/master/mod.rs +++ b/tools/wta/src/master/mod.rs @@ -495,13 +495,6 @@ impl acp::Client for MasterClient { session_id = ?sid, "forwarding fs/write_text_file to helper" ); - tracing::trace!( - target: "master.content", - op = "write_text_file", - session_id = ?sid, - path = ?args.path, - "fs/write_text_file path" - ); forwarder.write_text_file(args).await } @@ -519,13 +512,6 @@ impl acp::Client for MasterClient { session_id = ?sid, "forwarding fs/read_text_file to helper" ); - tracing::trace!( - target: "master.content", - op = "read_text_file", - session_id = ?sid, - path = ?args.path, - "fs/read_text_file path" - ); forwarder.read_text_file(args).await } @@ -544,14 +530,6 @@ impl acp::Client for MasterClient { args_len = args.args.len(), "forwarding terminal/create to helper" ); - // Command line can carry user/file content — trace only. - tracing::trace!( - target: "master.content", - session_id = ?sid, - command = %args.command, - args = ?args.args, - "create_terminal command" - ); forwarder.create_terminal(args).await } @@ -2322,12 +2300,6 @@ where title_len = disk_title.chars().count(), "upgraded synthetic title from on-disk session artefacts", ); - tracing::trace!( - target: "session_hook.content", - session_id = %sid.0, - new_title = %disk_title, - "upgraded synthetic title", - ); } upgraded } diff --git a/tools/wta/src/protocol/acp/client.rs b/tools/wta/src/protocol/acp/client.rs index 15e17a5d2..02c0e360f 100644 --- a/tools/wta/src/protocol/acp/client.rs +++ b/tools/wta/src/protocol/acp/client.rs @@ -929,13 +929,12 @@ fn truncate_for_prompt(text: &str, max_chars: usize) -> String { fn format_pane_context_summary(pane_context: Option<&PaneContext>) -> String { match pane_context { Some(context) => format!( - "pane_id={:?} tab_id={:?} window_id={:?} source_pane_id={:?} effective_source_pane_id={:?} cwd={:?}", + "pane_id={:?} tab_id={:?} window_id={:?} source_pane_id={:?} effective_source_pane_id={:?}", context.pane_id, context.tab_id, context.window_id, context.source_pane_id, context.effective_source_pane_id(), - context.cwd ), None => "none".to_string(), } @@ -3331,7 +3330,6 @@ async fn run_inner( let cwd = active_pane_cwd .clone() .unwrap_or_else(|| std::env::current_dir().unwrap_or_default()); - startup_probe.log(&format!("Using session cwd={}", cwd.display())); let session_future = conn.new_session(acp::NewSessionRequest::new(cwd)); let session = tokio::time::timeout(std::time::Duration::from_secs(15), session_future) .await From 81fa0f448c2a3204d4f4ea975dd32a35f75451a5 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Thu, 11 Jun 2026 15:59:56 +0800 Subject: [PATCH 08/12] Restore hook installer path diagnostics Hook installation and upgrade flows rely on concrete filesystem paths for troubleshooting stale marketplace and staging issues, so keep those path fields in agent_hooks logs.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/wta/src/agent_hooks_installer.rs | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tools/wta/src/agent_hooks_installer.rs b/tools/wta/src/agent_hooks_installer.rs index f1e6cff92..32b78d3ab 100644 --- a/tools/wta/src/agent_hooks_installer.rs +++ b/tools/wta/src/agent_hooks_installer.rs @@ -590,6 +590,7 @@ fn install_for_claude(home: &Path) { tracing::warn!( target: "agent_hooks", err = %e, + path = %settings_path.display(), "failed to strip legacy wta hooks from settings.json; non-fatal", ); } @@ -741,6 +742,8 @@ fn maybe_stage_bundle_for_codex(source: &Path) -> Option { Ok(()) => { tracing::info!( target: "agent_hooks", + source = %source.display(), + staged = %staged.display(), "restaged codex bundle out of WindowsApps", ); Some(staged) @@ -749,6 +752,8 @@ fn maybe_stage_bundle_for_codex(source: &Path) -> Option { tracing::warn!( target: "agent_hooks", err = %e, + source = %source.display(), + staged = %staged.display(), "failed to restage codex bundle out of WindowsApps; using original path", ); None @@ -798,6 +803,7 @@ fn install_for_copilot(home: &Path) { tracing::warn!( target: "copilot_hooks", err = %e, + path = %settings_path.display(), "failed to clean up stale wt-local marketplace entry; non-fatal", ); } @@ -846,11 +852,13 @@ fn install_for_copilot(home: &Path) { tracing::warn!( target: "copilot_hooks", err = %e, + path = %stale.display(), "failed to remove stale _direct folder; non-fatal", ); } else { tracing::info!( target: "copilot_hooks", + path = %stale.display(), "removed stale _direct plugin folder", ); } @@ -2217,6 +2225,8 @@ fn maybe_stage_bundle_for_claude(source: &Path) -> Option { Ok(()) => { tracing::info!( target: "agent_hooks", + source = %source.display(), + staged = %staged.display(), "staged claude bundle out of WindowsApps to sidestep Node.js cpSync EPERM", ); Some(staged) @@ -2225,6 +2235,8 @@ fn maybe_stage_bundle_for_claude(source: &Path) -> Option { tracing::warn!( target: "agent_hooks", err = %e, + source = %source.display(), + staged = %staged.display(), "failed to stage claude bundle under LOCALAPPDATA; \ falling back to WindowsApps source (claude plugin install \ may fail with EPERM)", @@ -2295,6 +2307,7 @@ fn cleanup_legacy_claude_hooks(settings_path: &Path) -> std::io::Result<()> { tracing::warn!( target: "agent_hooks", err = %e, + path = %settings_path.display(), "settings.json malformed; leaving untouched", ); return Ok(()); @@ -2343,6 +2356,7 @@ fn cleanup_legacy_claude_hooks(settings_path: &Path) -> std::io::Result<()> { fs::write(settings_path, serialized)?; tracing::info!( target: "agent_hooks", + path = %settings_path.display(), "stripped legacy wta hooks block", ); Ok(()) @@ -2426,6 +2440,7 @@ fn cleanup_stale_copilot_marketplace( tracing::warn!( target: "copilot_hooks", err = %e, + path = %settings_path.display(), "settings.json malformed; leaving untouched", ); return Ok(()); @@ -2433,6 +2448,7 @@ fn cleanup_stale_copilot_marketplace( }; let expected_str = expected_source.to_string_lossy().into_owned(); + let old_path: String; { let Some(root) = settings.as_object_mut() else { @@ -2470,6 +2486,7 @@ fn cleanup_stale_copilot_marketplace( } source.insert("path".to_string(), Value::String(expected_str.clone())); + old_path = current; } let serialized = serde_json::to_string_pretty(&settings) @@ -2477,6 +2494,9 @@ fn cleanup_stale_copilot_marketplace( fs::write(settings_path, serialized)?; tracing::info!( target: "copilot_hooks", + path = %settings_path.display(), + old = %old_path, + new = %expected_str, "rewrote stale wt-local marketplace path", ); Ok(()) @@ -3155,6 +3175,7 @@ fn save_upgrade_state(path: &Path, state: &UpgradeState) { tracing::warn!( target: "agent_hooks", err = %e, + path = %parent.display(), "failed to create upgrade-state parent dir", ); return; @@ -3171,6 +3192,7 @@ fn save_upgrade_state(path: &Path, state: &UpgradeState) { tracing::warn!( target: "agent_hooks", err = %e, + path = %path.display(), "failed to write upgrade-state file", ); } @@ -3204,6 +3226,7 @@ fn cleanup_stale_claude_marketplace( tracing::warn!( target: "agent_hooks", err = %e, + path = %known_path.display(), "known_marketplaces.json malformed; leaving untouched", ); return Ok(()); @@ -3212,6 +3235,7 @@ fn cleanup_stale_claude_marketplace( let expected_str = expected_source.to_string_lossy().into_owned(); let mut changed = false; + let mut old_path = String::new(); { let Some(root) = settings.as_object_mut() else { @@ -3230,6 +3254,7 @@ fn cleanup_stale_claude_marketplace( .to_string(); if !paths_equivalent(Path::new(¤t), expected_source) { source.insert("path".to_string(), Value::String(expected_str.clone())); + old_path = current; changed = true; } } @@ -3259,6 +3284,9 @@ fn cleanup_stale_claude_marketplace( fs::write(known_path, serialized)?; tracing::info!( target: "agent_hooks", + path = %known_path.display(), + old = %old_path, + new = %expected_str, "rewrote stale wt-local marketplace path (claude)", ); Ok(()) From e73dad1e5ea2d683d6b9aa46ed99e8ab5fddb42b Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Thu, 11 Jun 2026 16:02:54 +0800 Subject: [PATCH 09/12] Restore hook installer logging Return tools/wta/src/agent_hooks_installer.rs to origin/main so this PR does not change hook installer diagnostics.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/wta/src/agent_hooks_installer.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/wta/src/agent_hooks_installer.rs b/tools/wta/src/agent_hooks_installer.rs index 32b78d3ab..0adf1a130 100644 --- a/tools/wta/src/agent_hooks_installer.rs +++ b/tools/wta/src/agent_hooks_installer.rs @@ -2078,6 +2078,9 @@ fn run_plugin_cli_capture_with_env( tracing::warn!( target: "agent_hooks", exe = exe, + args = ?args, + stdout = %stdout.trim(), + stderr = %stderr.trim(), status = ?output.status.code(), "plugin CLI returned non-zero exit", ); @@ -2085,6 +2088,8 @@ fn run_plugin_cli_capture_with_env( tracing::info!( target: "agent_hooks", exe = exe, + args = ?args, + stdout = %stdout.trim(), "plugin CLI succeeded", ); } @@ -2143,6 +2148,7 @@ fn run_plugin_cli_with_env( tracing::info!( target: "agent_hooks", exe = exe, + args = ?args, "plugin CLI exited non-zero but matched idempotency substring; treating as success", ); return Ok(()); From 2eaf52567b2d37cbdee54a1b90b019224903d1f3 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Thu, 11 Jun 2026 16:10:30 +0800 Subject: [PATCH 10/12] Add stable master pipe log hints Log the fixed discovery filename and pipe name around master pipe discovery file operations without including the user-specific filesystem path.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/wta/src/master/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/wta/src/master/mod.rs b/tools/wta/src/master/mod.rs index 7e04474ff..4743b87c1 100644 --- a/tools/wta/src/master/mod.rs +++ b/tools/wta/src/master/mod.rs @@ -46,6 +46,7 @@ use std::sync::{Arc, OnceLock, Weak}; /// helper sharing this master. const NOTIF_CHANNEL_CAPACITY: usize = 1024; const SESSION_NEW_TIMEOUT_SECS: u64 = 120; +const MASTER_PIPE_DISCOVERY_FILE: &str = "master-pipe.txt"; use acp::Agent as _; use acp::Client as _; @@ -1301,6 +1302,8 @@ impl MasterPipeDiscoveryGuard { if let Err(err) = std::fs::create_dir_all(parent) { tracing::warn!( target: "master", + discovery_file = MASTER_PIPE_DISCOVERY_FILE, + pipe_name = %pipe_name, error = %err, "failed to create master pipe discovery directory" ); @@ -1313,12 +1316,15 @@ impl MasterPipeDiscoveryGuard { match std::fs::write(path, pipe_name) { Ok(()) => tracing::info!( target: "master", + discovery_file = MASTER_PIPE_DISCOVERY_FILE, pipe_name = %pipe_name, "master pipe discovery file written" ), Err(err) => { tracing::warn!( target: "master", + discovery_file = MASTER_PIPE_DISCOVERY_FILE, + pipe_name = %pipe_name, error = %err, "failed to write master pipe discovery file" ); @@ -1348,6 +1354,8 @@ impl Drop for MasterPipeDiscoveryGuard { if let Err(err) = std::fs::remove_file(path) { tracing::warn!( target: "master", + discovery_file = MASTER_PIPE_DISCOVERY_FILE, + pipe_name = %self.pipe_name, error = %err, "failed to remove master pipe discovery file" ); From e28ebb8fcdec57d9b8054b238d68b8e993db9d59 Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Thu, 11 Jun 2026 16:42:17 +0800 Subject: [PATCH 11/12] Avoid cwd in session hook diagnostics Log safe SessionEvent summaries instead of full debug payloads so SessionStarted cwd/title do not appear in helper/master logs. Also strip user prompt template paths from prompt-source diagnostics and remove startup-probe cwd details.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/wta/src/agent_sessions.rs | 30 ++++++++++++++++++++++++++++ tools/wta/src/master/mod.rs | 4 ++-- tools/wta/src/protocol/acp/client.rs | 15 +++++++------- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/tools/wta/src/agent_sessions.rs b/tools/wta/src/agent_sessions.rs index 024b4a863..6e810b578 100644 --- a/tools/wta/src/agent_sessions.rs +++ b/tools/wta/src/agent_sessions.rs @@ -285,6 +285,36 @@ pub enum SessionEvent { ResumePaneAssigned { key: AgentKey, pane_session_id: String }, } +pub fn session_event_log_summary(event: &SessionEvent) -> String { + match event { + SessionEvent::SessionStarted { + key, + cli_source, + pane_session_id, + .. + } => format!( + "kind=SessionStarted key={key} cli_source={cli_source:?} pane_session_id={pane_session_id}" + ), + SessionEvent::ToolStarting { key, tool_name } => { + format!("kind=ToolStarting key={key} tool_name={tool_name}") + } + SessionEvent::ToolCompleted { key } => format!("kind=ToolCompleted key={key}"), + SessionEvent::Notification { key, .. } => format!("kind=Notification key={key}"), + SessionEvent::SessionStopped { key, .. } => format!("kind=SessionStopped key={key}"), + SessionEvent::ConnectionFailed { + pane_session_id, .. + } => format!("kind=ConnectionFailed pane_session_id={pane_session_id}"), + SessionEvent::PaneClosed { pane_session_id } => { + format!("kind=PaneClosed pane_session_id={pane_session_id}") + } + SessionEvent::ResumeDispatched { key } => format!("kind=ResumeDispatched key={key}"), + SessionEvent::ResumePaneAssigned { + key, + pane_session_id, + } => format!("kind=ResumePaneAssigned key={key} pane_session_id={pane_session_id}"), + } +} + /// Returns `true` for tool names that represent the agent soliciting input /// from the user (a clarifying question or a forced-choice prompt) rather /// than running an autonomous task. Such tools never auto-complete — they diff --git a/tools/wta/src/master/mod.rs b/tools/wta/src/master/mod.rs index 4743b87c1..ccdf8e9cc 100644 --- a/tools/wta/src/master/mod.rs +++ b/tools/wta/src/master/mod.rs @@ -2090,7 +2090,7 @@ async fn handle_session_hook( tracing::info!( target: "session_hook", - event = ?event, + event = %crate::agent_sessions::session_event_log_summary(&event), "received helper session hook" ); @@ -2192,7 +2192,7 @@ async fn handle_master_wt_event( target: "master_wt_event", pane_id = %pane_id, state = %pane_state, - event = ?event, + event = %crate::agent_sessions::session_event_log_summary(&event), "applying WT connection_state event to master registry" ); let applied = state.registry.apply_event(event).await; diff --git a/tools/wta/src/protocol/acp/client.rs b/tools/wta/src/protocol/acp/client.rs index 02c0e360f..f3e824654 100644 --- a/tools/wta/src/protocol/acp/client.rs +++ b/tools/wta/src/protocol/acp/client.rs @@ -1456,7 +1456,7 @@ fn acp_log_built_prompt( target: "acp", user_text_len = user_text.len(), pane_context = %format_pane_context_summary(pane_context), - prompt_source, + prompt_source = prompt_source_log_label(prompt_source), "planner_prompt_begin" ); // Full assembled prompt = user text + captured terminal buffer + cwd. @@ -1465,6 +1465,10 @@ fn acp_log_built_prompt( tracing::debug!(target: "acp", "planner_prompt_end"); } +fn prompt_source_log_label(source: &str) -> &str { + source.split_once(':').map_or(source, |(kind, _)| kind) +} + /// Per-turn audit log: one structured info-level line per round. /// /// Use this to verify rounds 2+ on a session are "clean" — i.e. the @@ -2625,13 +2629,13 @@ pub async fn run_acp_client_over_pipe( match conn_for_hook.ext_method(req).await { Ok(response) => tracing::debug!( target: "session_hook", - event = ?event, + event = %crate::agent_sessions::session_event_log_summary(&event), response = %response.0.get(), "session_hook sent to master" ), Err(err) => tracing::warn!( target: "session_hook", - event = ?event, + event = %crate::agent_sessions::session_event_log_summary(&event), error = ?err, "session_hook ext-request to master failed" ), @@ -3150,10 +3154,7 @@ async fn run_inner( // packaged identity — that's the bug we saw where `cargo run` resolved // against C:\Users\ and failed to find Cargo.toml). let active_pane_cwd = resolve_active_pane_cwd(&shell_mgr, wt_connected).await; - startup_probe.log(&format!( - "resolved active pane cwd: {:?}", - active_pane_cwd.as_ref().map(|p| p.display().to_string()) - )); + startup_probe.log("resolved active pane cwd"); let spawned = crate::protocol::acp::spawn::spawn_agent_process(agent_cmd, active_pane_cwd.as_deref())?; From 3706b9f32623b86f5debe10021b3382df98cb18c Mon Sep 17 00:00:00 2001 From: "Kai Tao (from Dev Box)" Date: Thu, 11 Jun 2026 16:43:57 +0800 Subject: [PATCH 12/12] Revert "Avoid cwd in session hook diagnostics" This reverts commit e28ebb8fcdec57d9b8054b238d68b8e993db9d59. --- tools/wta/src/agent_sessions.rs | 30 ---------------------------- tools/wta/src/master/mod.rs | 4 ++-- tools/wta/src/protocol/acp/client.rs | 15 +++++++------- 3 files changed, 9 insertions(+), 40 deletions(-) diff --git a/tools/wta/src/agent_sessions.rs b/tools/wta/src/agent_sessions.rs index 6e810b578..024b4a863 100644 --- a/tools/wta/src/agent_sessions.rs +++ b/tools/wta/src/agent_sessions.rs @@ -285,36 +285,6 @@ pub enum SessionEvent { ResumePaneAssigned { key: AgentKey, pane_session_id: String }, } -pub fn session_event_log_summary(event: &SessionEvent) -> String { - match event { - SessionEvent::SessionStarted { - key, - cli_source, - pane_session_id, - .. - } => format!( - "kind=SessionStarted key={key} cli_source={cli_source:?} pane_session_id={pane_session_id}" - ), - SessionEvent::ToolStarting { key, tool_name } => { - format!("kind=ToolStarting key={key} tool_name={tool_name}") - } - SessionEvent::ToolCompleted { key } => format!("kind=ToolCompleted key={key}"), - SessionEvent::Notification { key, .. } => format!("kind=Notification key={key}"), - SessionEvent::SessionStopped { key, .. } => format!("kind=SessionStopped key={key}"), - SessionEvent::ConnectionFailed { - pane_session_id, .. - } => format!("kind=ConnectionFailed pane_session_id={pane_session_id}"), - SessionEvent::PaneClosed { pane_session_id } => { - format!("kind=PaneClosed pane_session_id={pane_session_id}") - } - SessionEvent::ResumeDispatched { key } => format!("kind=ResumeDispatched key={key}"), - SessionEvent::ResumePaneAssigned { - key, - pane_session_id, - } => format!("kind=ResumePaneAssigned key={key} pane_session_id={pane_session_id}"), - } -} - /// Returns `true` for tool names that represent the agent soliciting input /// from the user (a clarifying question or a forced-choice prompt) rather /// than running an autonomous task. Such tools never auto-complete — they diff --git a/tools/wta/src/master/mod.rs b/tools/wta/src/master/mod.rs index ccdf8e9cc..4743b87c1 100644 --- a/tools/wta/src/master/mod.rs +++ b/tools/wta/src/master/mod.rs @@ -2090,7 +2090,7 @@ async fn handle_session_hook( tracing::info!( target: "session_hook", - event = %crate::agent_sessions::session_event_log_summary(&event), + event = ?event, "received helper session hook" ); @@ -2192,7 +2192,7 @@ async fn handle_master_wt_event( target: "master_wt_event", pane_id = %pane_id, state = %pane_state, - event = %crate::agent_sessions::session_event_log_summary(&event), + event = ?event, "applying WT connection_state event to master registry" ); let applied = state.registry.apply_event(event).await; diff --git a/tools/wta/src/protocol/acp/client.rs b/tools/wta/src/protocol/acp/client.rs index f3e824654..02c0e360f 100644 --- a/tools/wta/src/protocol/acp/client.rs +++ b/tools/wta/src/protocol/acp/client.rs @@ -1456,7 +1456,7 @@ fn acp_log_built_prompt( target: "acp", user_text_len = user_text.len(), pane_context = %format_pane_context_summary(pane_context), - prompt_source = prompt_source_log_label(prompt_source), + prompt_source, "planner_prompt_begin" ); // Full assembled prompt = user text + captured terminal buffer + cwd. @@ -1465,10 +1465,6 @@ fn acp_log_built_prompt( tracing::debug!(target: "acp", "planner_prompt_end"); } -fn prompt_source_log_label(source: &str) -> &str { - source.split_once(':').map_or(source, |(kind, _)| kind) -} - /// Per-turn audit log: one structured info-level line per round. /// /// Use this to verify rounds 2+ on a session are "clean" — i.e. the @@ -2629,13 +2625,13 @@ pub async fn run_acp_client_over_pipe( match conn_for_hook.ext_method(req).await { Ok(response) => tracing::debug!( target: "session_hook", - event = %crate::agent_sessions::session_event_log_summary(&event), + event = ?event, response = %response.0.get(), "session_hook sent to master" ), Err(err) => tracing::warn!( target: "session_hook", - event = %crate::agent_sessions::session_event_log_summary(&event), + event = ?event, error = ?err, "session_hook ext-request to master failed" ), @@ -3154,7 +3150,10 @@ async fn run_inner( // packaged identity — that's the bug we saw where `cargo run` resolved // against C:\Users\ and failed to find Cargo.toml). let active_pane_cwd = resolve_active_pane_cwd(&shell_mgr, wt_connected).await; - startup_probe.log("resolved active pane cwd"); + startup_probe.log(&format!( + "resolved active pane cwd: {:?}", + active_pane_cwd.as_ref().map(|p| p.display().to_string()) + )); let spawned = crate::protocol::acp::spawn::spawn_agent_process(agent_cmd, active_pane_cwd.as_deref())?;