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 <noreply@anthropic.com>
This commit is contained in:
Dave
2026-02-26 17:01:38 +00:00
parent b34335daad
commit ac087f1a58
5 changed files with 36 additions and 3 deletions

View File

@@ -1580,7 +1580,17 @@ async fn tool_merge_agent_work(args: &Value, ctx: &AppContext) -> Result<String,
.ok_or("Missing required argument: story_id")?; .ok_or("Missing required argument: story_id")?;
let agent_name = args.get("agent_name").and_then(|v| v.as_str()); let agent_name = args.get("agent_name").and_then(|v| v.as_str());
// TRACE:MERGE-DEBUG — remove once root cause is found
crate::slog!(
"[MERGE-DEBUG] tool_merge_agent_work called for story_id={:?}, agent_name={:?}",
story_id,
agent_name
);
let project_root = ctx.agents.get_project_root(&ctx.state)?; let project_root = ctx.agents.get_project_root(&ctx.state)?;
crate::slog!(
"[MERGE-DEBUG] tool_merge_agent_work: project_root resolved to {:?}",
project_root
);
let report = ctx.agents.merge_agent_work(&project_root, story_id).await?; let report = ctx.agents.merge_agent_work(&project_root, story_id).await?;
let status_msg = if report.success && report.gates_passed && report.conflicts_resolved { let status_msg = if report.success && report.gates_passed && report.conflicts_resolved {

View File

@@ -49,6 +49,11 @@ impl ProjectApi {
/// Close the current project and clear the stored selection. /// Close the current project and clear the stored selection.
#[oai(path = "/project", method = "delete")] #[oai(path = "/project", method = "delete")]
async fn close_project(&self) -> OpenApiResult<Json<bool>> { async fn close_project(&self) -> OpenApiResult<Json<bool>> {
// 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)?; fs::close_project(&self.ctx.state, self.ctx.store.as_ref()).map_err(bad_request)?;
Ok(Json(true)) Ok(Json(true))
} }

View File

@@ -532,6 +532,8 @@ pub async fn open_project(
let _ = worktree_write_mcp_json(&p, port); 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())?; let mut root = state.project_root.lock().map_err(|e| e.to_string())?;
*root = Some(p); *root = Some(p);
} }
@@ -551,6 +553,8 @@ pub async fn open_project(
pub fn close_project(state: &SessionState, store: &dyn StoreOps) -> Result<(), String> { 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())?; let mut root = state.project_root.lock().map_err(|e| e.to_string())?;
*root = None; *root = None;
} }
@@ -579,6 +583,12 @@ pub fn get_current_project(
{ {
let p = PathBuf::from(path_str); let p = PathBuf::from(path_str);
if p.exists() && p.is_dir() { 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())?; let mut root = state.project_root.lock().map_err(|e| e.to_string())?;
*root = Some(p); *root = Some(p);
return Ok(Some(path_str.to_string())); return Ok(Some(path_str.to_string()));

View File

@@ -94,6 +94,8 @@ async fn main() -> Result<(), std::io::Error> {
.unwrap_or_else(|e| panic!("Invalid project.toml: {e}")); .unwrap_or_else(|e| panic!("Invalid project.toml: {e}"));
} else { } else {
// No .story_kit/ found — fall back to cwd so existing behaviour is preserved. // 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()); *app_state.project_root.lock().unwrap() = Some(cwd.clone());
} }
} }

View File

@@ -22,9 +22,15 @@ impl Default for SessionState {
impl SessionState { impl SessionState {
pub fn get_project_root(&self) -> Result<PathBuf, String> { pub fn get_project_root(&self) -> Result<PathBuf, String> {
let root_guard = self.project_root.lock().map_err(|e| e.to_string())?; let root_guard = self.project_root.lock().map_err(|e| e.to_string())?;
let root = root_guard let root = root_guard.as_ref().ok_or_else(|| {
.as_ref() // TRACE:MERGE-DEBUG — remove once root cause is found
.ok_or_else(|| "No project is currently open.".to_string())?; 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()) Ok(root.clone())
} }
} }