story-kit: merge 172_bug_setup_command_failure_prevents_agent_from_starting_creating_unrecoverable_deadlock

This commit is contained in:
Dave
2026-02-24 23:29:56 +00:00
parent 5807c1f5a6
commit ba8dde1aa8

View File

@@ -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();