story-kit: merge 172_bug_setup_command_failure_prevents_agent_from_starting_creating_unrecoverable_deadlock
This commit is contained in:
@@ -79,7 +79,7 @@ pub async fn create_worktree(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("spawn_blocking: {e}"))??;
|
.map_err(|e| format!("spawn_blocking: {e}"))??;
|
||||||
write_mcp_json(&wt_path, port)?;
|
write_mcp_json(&wt_path, port)?;
|
||||||
run_setup_commands(&wt_path, config).await?;
|
run_setup_commands(&wt_path, config).await;
|
||||||
return Ok(WorktreeInfo {
|
return Ok(WorktreeInfo {
|
||||||
path: wt_path,
|
path: wt_path,
|
||||||
branch,
|
branch,
|
||||||
@@ -95,7 +95,7 @@ pub async fn create_worktree(
|
|||||||
.map_err(|e| format!("spawn_blocking: {e}"))??;
|
.map_err(|e| format!("spawn_blocking: {e}"))??;
|
||||||
|
|
||||||
write_mcp_json(&wt_path, port)?;
|
write_mcp_json(&wt_path, port)?;
|
||||||
run_setup_commands(&wt_path, config).await?;
|
run_setup_commands(&wt_path, config).await;
|
||||||
|
|
||||||
Ok(WorktreeInfo {
|
Ok(WorktreeInfo {
|
||||||
path: wt_path,
|
path: wt_path,
|
||||||
@@ -258,14 +258,15 @@ fn remove_worktree_sync(
|
|||||||
Ok(())
|
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 {
|
for component in &config.component {
|
||||||
let cmd_dir = wt_path.join(&component.path);
|
let cmd_dir = wt_path.join(&component.path);
|
||||||
for cmd in &component.setup {
|
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> {
|
async fn run_teardown_commands(wt_path: &Path, config: &ProjectConfig) -> Result<(), String> {
|
||||||
@@ -491,7 +492,8 @@ mod tests {
|
|||||||
component: vec![],
|
component: vec![],
|
||||||
agent: 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]
|
#[tokio::test]
|
||||||
@@ -506,11 +508,12 @@ mod tests {
|
|||||||
}],
|
}],
|
||||||
agent: 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]
|
#[tokio::test]
|
||||||
async fn run_setup_commands_propagates_failure() {
|
async fn run_setup_commands_ignores_failures() {
|
||||||
let tmp = TempDir::new().unwrap();
|
let tmp = TempDir::new().unwrap();
|
||||||
let config = ProjectConfig {
|
let config = ProjectConfig {
|
||||||
component: vec![ComponentConfig {
|
component: vec![ComponentConfig {
|
||||||
@@ -521,9 +524,8 @@ mod tests {
|
|||||||
}],
|
}],
|
||||||
agent: vec![],
|
agent: vec![],
|
||||||
};
|
};
|
||||||
let result = run_setup_commands(tmp.path(), &config).await;
|
// Setup command failures are non-fatal — should not panic or propagate
|
||||||
assert!(result.is_err(), "Expected failure from failing setup command");
|
run_setup_commands(tmp.path(), &config).await;
|
||||||
assert!(result.unwrap_err().contains("failed"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -643,6 +645,70 @@ mod tests {
|
|||||||
assert!(result.is_ok(), "Expected removal to succeed: {:?}", result.err());
|
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]
|
#[tokio::test]
|
||||||
async fn remove_worktree_async_removes_directory() {
|
async fn remove_worktree_async_removes_directory() {
|
||||||
let tmp = TempDir::new().unwrap();
|
let tmp = TempDir::new().unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user