Make merge_agent_work async to avoid MCP 60-second tool timeout
The merge pipeline (squash merge + quality gates) takes well over 60 seconds. Claude Code's MCP HTTP transport times out at 60s, causing "completed with no output" — the mergemaster retries fruitlessly. merge_agent_work now starts the pipeline as a background task and returns immediately. A new get_merge_status tool lets the mergemaster poll until the job reaches a terminal state. Also adds a double-start guard so concurrent calls for the same story are rejected. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -7,6 +8,29 @@ use crate::config::ProjectConfig;
|
||||
|
||||
use super::gates::run_project_tests;
|
||||
|
||||
/// Global lock ensuring only one squash-merge runs at a time.
|
||||
///
|
||||
/// The merge pipeline uses a shared `.story_kit/merge_workspace` directory and
|
||||
/// temporary `merge-queue/{story_id}` branches. If two merges run concurrently,
|
||||
/// the second call's initial cleanup destroys the first call's branch mid-flight,
|
||||
/// causing `git cherry-pick merge-queue/…` to fail with "bad revision".
|
||||
static MERGE_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
/// Status of an async merge job.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub enum MergeJobStatus {
|
||||
Running,
|
||||
Completed(MergeReport),
|
||||
Failed(String),
|
||||
}
|
||||
|
||||
/// Tracks a background merge job started by `merge_agent_work`.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct MergeJob {
|
||||
pub story_id: String,
|
||||
pub status: MergeJobStatus,
|
||||
}
|
||||
|
||||
/// Result of a mergemaster merge operation.
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct MergeReport {
|
||||
@@ -57,6 +81,11 @@ pub(crate) fn run_squash_merge(
|
||||
branch: &str,
|
||||
story_id: &str,
|
||||
) -> Result<SquashMergeResult, String> {
|
||||
// Acquire the merge lock so concurrent calls don't clobber each other.
|
||||
let _lock = MERGE_LOCK
|
||||
.lock()
|
||||
.map_err(|e| format!("Merge lock poisoned: {e}"))?;
|
||||
|
||||
let mut all_output = String::new();
|
||||
let merge_branch = format!("merge-queue/{story_id}");
|
||||
let merge_wt_path = project_root
|
||||
|
||||
Reference in New Issue
Block a user