diff --git a/internal/config/config.go b/internal/config/config.go index ddd06bd..b5cac65 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -168,6 +168,11 @@ type MonitorConfig struct { PollIntervalMs int `yaml:"poll_interval_ms"` StuckThresholdS int `yaml:"stuck_threshold_s"` ContextFreshnessTokens int `yaml:"context_freshness_tokens"` + // PipelineTimeoutS bounds the whole post-execution pipeline (review + QA + + // merge/conflict-resolution) per story. Slow local LLM calls plus rebase- + // conflict resolution under concurrent builds can exceed a tight limit and + // trip "context deadline exceeded". Default 900s. + PipelineTimeoutS int `yaml:"pipeline_timeout_s"` } // ControllerConfig configures the periodic active controller. diff --git a/internal/config/loader.go b/internal/config/loader.go index 288b144..548e533 100644 --- a/internal/config/loader.go +++ b/internal/config/loader.go @@ -55,6 +55,7 @@ func DefaultConfig() Config { PollIntervalMs: 10000, StuckThresholdS: 120, ContextFreshnessTokens: 150000, + PipelineTimeoutS: 900, // 15 min — room for slow local LLM calls + conflict resolution }, Cleanup: CleanupConfig{ WorktreePrune: "immediate", diff --git a/internal/engine/monitor.go b/internal/engine/monitor.go index b4001f9..5aff484 100644 --- a/internal/engine/monitor.go +++ b/internal/engine/monitor.go @@ -387,9 +387,14 @@ func (m *Monitor) postExecutionPipeline(ctx context.Context, ag ActiveAgent, rep // would mark the story DB as devdb.OutcomeFailed and pollute metrics. parentCtx := ctx - // Wrap context with a 5-minute timeout to prevent pipeline LLM calls - // (review, QA, conflict resolution) from blocking indefinitely. - pipelineCtx, pipelineCancel := context.WithTimeout(ctx, 5*time.Minute) + // Bound the post-execution pipeline (review, QA, conflict resolution). + // Configurable because slow local LLM calls + conflict resolution under + // concurrent builds can exceed a tight limit. Falls back to 15m when unset. + pipelineTimeout := time.Duration(m.config.Monitor.PipelineTimeoutS) * time.Second + if pipelineTimeout <= 0 { + pipelineTimeout = 15 * time.Minute + } + pipelineCtx, pipelineCancel := context.WithTimeout(ctx, pipelineTimeout) defer pipelineCancel() ctx = pipelineCtx diff --git a/nxd.config.example.yaml b/nxd.config.example.yaml index e80358c..6859ca2 100644 --- a/nxd.config.example.yaml +++ b/nxd.config.example.yaml @@ -53,6 +53,7 @@ monitor: poll_interval_ms: 10000 stuck_threshold_s: 120 context_freshness_tokens: 150000 + pipeline_timeout_s: 900 cleanup: worktree_prune: immediate branch_retention_days: 7