diff --git a/server/src/worktree.rs b/server/src/worktree.rs index a655f2e..3e034f0 100644 --- a/server/src/worktree.rs +++ b/server/src/worktree.rs @@ -79,7 +79,7 @@ pub async fn create_worktree( .await .map_err(|e| format!("spawn_blocking: {e}"))??; write_mcp_json(&wt_path, port)?; - run_setup_commands(&wt_path, config).await?; + run_setup_commands(&wt_path, config).await; return Ok(WorktreeInfo { path: wt_path, branch, @@ -95,7 +95,7 @@ pub async fn create_worktree( .map_err(|e| format!("spawn_blocking: {e}"))??; write_mcp_json(&wt_path, port)?; - run_setup_commands(&wt_path, config).await?; + run_setup_commands(&wt_path, config).await; Ok(WorktreeInfo { path: wt_path, @@ -258,14 +258,15 @@ fn remove_worktree_sync( Ok(()) } -async fn run_setup_commands(wt_path: &Path, config: &ProjectConfig) -> Result<(), String> { +async fn run_setup_commands(wt_path: &Path, config: &ProjectConfig) { for component in &config.component { let cmd_dir = wt_path.join(&component.path); for cmd in &component.setup { - run_shell_command(cmd, &cmd_dir).await?; + if let Err(e) = run_shell_command(cmd, &cmd_dir).await { + slog!("[worktree] setup warning for {}: {e}", component.name); + } } } - Ok(()) } async fn run_teardown_commands(wt_path: &Path, config: &ProjectConfig) -> Result<(), String> { @@ -491,7 +492,8 @@ mod tests { component: vec![], agent: vec![], }; - assert!(run_setup_commands(tmp.path(), &config).await.is_ok()); + // Should complete without panic + run_setup_commands(tmp.path(), &config).await; } #[tokio::test] @@ -506,11 +508,12 @@ mod tests { }], agent: vec![], }; - assert!(run_setup_commands(tmp.path(), &config).await.is_ok()); + // Should complete without panic + run_setup_commands(tmp.path(), &config).await; } #[tokio::test] - async fn run_setup_commands_propagates_failure() { + async fn run_setup_commands_ignores_failures() { let tmp = TempDir::new().unwrap(); let config = ProjectConfig { component: vec![ComponentConfig { @@ -521,9 +524,8 @@ mod tests { }], agent: vec![], }; - let result = run_setup_commands(tmp.path(), &config).await; - assert!(result.is_err(), "Expected failure from failing setup command"); - assert!(result.unwrap_err().contains("failed")); + // Setup command failures are non-fatal — should not panic or propagate + run_setup_commands(tmp.path(), &config).await; } #[tokio::test] @@ -643,6 +645,70 @@ mod tests { assert!(result.is_ok(), "Expected removal to succeed: {:?}", result.err()); } + #[tokio::test] + async fn create_worktree_succeeds_despite_setup_failure() { + let tmp = TempDir::new().unwrap(); + let project_root = tmp.path().join("my-project"); + fs::create_dir_all(&project_root).unwrap(); + init_git_repo(&project_root); + + let config = ProjectConfig { + component: vec![ComponentConfig { + name: "broken-build".to_string(), + path: ".".to_string(), + setup: vec!["exit 1".to_string()], + teardown: vec![], + }], + agent: vec![], + }; + // Even though setup commands fail, create_worktree must succeed + // so the agent can start and fix the problem itself. + let result = create_worktree(&project_root, "172_setup_fail", &config, 3001).await; + assert!( + result.is_ok(), + "create_worktree must succeed even if setup commands fail: {:?}", + result.err() + ); + let info = result.unwrap(); + assert!(info.path.exists()); + assert!(info.path.join(".mcp.json").exists()); + } + + #[tokio::test] + async fn create_worktree_reuse_succeeds_despite_setup_failure() { + let tmp = TempDir::new().unwrap(); + let project_root = tmp.path().join("my-project"); + fs::create_dir_all(&project_root).unwrap(); + init_git_repo(&project_root); + + let empty_config = ProjectConfig { + component: vec![], + agent: vec![], + }; + // First creation — no setup commands, should succeed + create_worktree(&project_root, "173_reuse_fail", &empty_config, 3001) + .await + .unwrap(); + + let failing_config = ProjectConfig { + component: vec![ComponentConfig { + name: "broken-build".to_string(), + path: ".".to_string(), + setup: vec!["exit 1".to_string()], + teardown: vec![], + }], + agent: vec![], + }; + // Second call — worktree exists, setup commands fail, must still succeed + let result = + create_worktree(&project_root, "173_reuse_fail", &failing_config, 3002).await; + assert!( + result.is_ok(), + "create_worktree reuse must succeed even if setup commands fail: {:?}", + result.err() + ); + } + #[tokio::test] async fn remove_worktree_async_removes_directory() { let tmp = TempDir::new().unwrap();