From ac087f1a58b8607ff0d699790c21d6432d6325a1 Mon Sep 17 00:00:00 2001 From: Dave Date: Thu, 26 Feb 2026 17:01:38 +0000 Subject: [PATCH] chore: add MERGE-DEBUG traces for project_root lifecycle Temporary diagnostic logging to track why project_root becomes None during merge pipeline operations. Tagged with MERGE-DEBUG for easy grep-and-remove once the root cause is confirmed fixed. Co-Authored-By: Claude Opus 4.6 --- server/src/http/mcp.rs | 10 ++++++++++ server/src/http/project.rs | 5 +++++ server/src/io/fs.rs | 10 ++++++++++ server/src/main.rs | 2 ++ server/src/state.rs | 12 +++++++++--- 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/server/src/http/mcp.rs b/server/src/http/mcp.rs index eac30f7..7c7dd14 100644 --- a/server/src/http/mcp.rs +++ b/server/src/http/mcp.rs @@ -1580,7 +1580,17 @@ async fn tool_merge_agent_work(args: &Value, ctx: &AppContext) -> Result OpenApiResult> { + // TRACE:MERGE-DEBUG — remove once root cause is found + crate::slog_error!( + "[MERGE-DEBUG] DELETE /project called! \ + Backtrace: this is the only code path that clears project_root." + ); fs::close_project(&self.ctx.state, self.ctx.store.as_ref()).map_err(bad_request)?; Ok(Json(true)) } diff --git a/server/src/io/fs.rs b/server/src/io/fs.rs index bcec3f8..02080b3 100644 --- a/server/src/io/fs.rs +++ b/server/src/io/fs.rs @@ -532,6 +532,8 @@ pub async fn open_project( let _ = worktree_write_mcp_json(&p, port); { + // TRACE:MERGE-DEBUG — remove once root cause is found + crate::slog!("[MERGE-DEBUG] open_project: setting project_root to {:?}", p); let mut root = state.project_root.lock().map_err(|e| e.to_string())?; *root = Some(p); } @@ -551,6 +553,8 @@ pub async fn open_project( pub fn close_project(state: &SessionState, store: &dyn StoreOps) -> Result<(), String> { { + // TRACE:MERGE-DEBUG — remove once root cause is found + crate::slog!("[MERGE-DEBUG] close_project: setting project_root to None"); let mut root = state.project_root.lock().map_err(|e| e.to_string())?; *root = None; } @@ -579,6 +583,12 @@ pub fn get_current_project( { let p = PathBuf::from(path_str); if p.exists() && p.is_dir() { + // TRACE:MERGE-DEBUG — remove once root cause is found + crate::slog!( + "[MERGE-DEBUG] get_current_project: project_root was None, \ + restoring from store to {:?}", + p + ); let mut root = state.project_root.lock().map_err(|e| e.to_string())?; *root = Some(p); return Ok(Some(path_str.to_string())); diff --git a/server/src/main.rs b/server/src/main.rs index cad0225..17d477f 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -94,6 +94,8 @@ async fn main() -> Result<(), std::io::Error> { .unwrap_or_else(|e| panic!("Invalid project.toml: {e}")); } else { // No .story_kit/ found — fall back to cwd so existing behaviour is preserved. + // TRACE:MERGE-DEBUG — remove once root cause is found + slog!("[MERGE-DEBUG] main: no .story_kit/ found, falling back to cwd {:?}", cwd); *app_state.project_root.lock().unwrap() = Some(cwd.clone()); } } diff --git a/server/src/state.rs b/server/src/state.rs index db9baa7..4522fd5 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -22,9 +22,15 @@ impl Default for SessionState { impl SessionState { pub fn get_project_root(&self) -> Result { let root_guard = self.project_root.lock().map_err(|e| e.to_string())?; - let root = root_guard - .as_ref() - .ok_or_else(|| "No project is currently open.".to_string())?; + let root = root_guard.as_ref().ok_or_else(|| { + // TRACE:MERGE-DEBUG — remove once root cause is found + crate::slog_error!( + "[MERGE-DEBUG] get_project_root() called but project_root is None! \ + Backtrace hint: check caller in MCP tool handler." + ); + "No project is currently open.".to_string() + })?; Ok(root.clone()) } + }