diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 2095cb4..633cfb4 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -404,7 +404,19 @@ func newServer(pool *db.Pool, agentID uuid.UUID) *mcp.Server { pool.Exec(ctx, `UPDATE executions SET status='pending_approval', risk_class='config_mutation' WHERE entity_id=$1`, id) createApproval(ctx, pool, id, targetID, "apt_upgrade", params, "config_mutation") if approved := autoApprove(ctx, pool, id); approved { - go executeApprovedViaAPI(ctx, id, targetSlug, "apt_upgrade:"+params) + // context.Background(), NOT ctx: ctx is scoped to this + // MCP tool call, which ends (and cancels) as soon as the + // chat turn's HTTP response completes — normal, expected, + // happens on every turn. A goroutine meant to outlive the + // request must not inherit its context, or the async work + // dies silently the instant the turn ends. Found live: + // every assent-window auto-approved pct_create failed + // with "context canceled" the moment the triggering + // /chat request finished — exactly the same context- + // lifetime class of bug, in the one place it had been + // missed (httpapi's own approval goroutine already used + // context.Background() correctly). + go executeApprovedViaAPI(context.Background(), id, targetSlug, "apt_upgrade:"+params) slog.Info("mcp: apt_upgrade auto-approved via assent window", "execution_id", id) return textResult(fmt.Sprintf("apt_upgrade on %s auto-approved via assent window — execution %s running.", targetSlug, id)), nil } @@ -421,7 +433,7 @@ func newServer(pool *db.Pool, agentID uuid.UUID) *mcp.Server { pool.Exec(ctx, `UPDATE executions SET status='pending_approval', risk_class='config_mutation' WHERE entity_id=$1`, id) createApproval(ctx, pool, id, targetID, "pct_create", params, "config_mutation") if approved := autoApprove(ctx, pool, id); approved { - go executeApprovedViaAPI(ctx, id, targetSlug, "pct_create:"+params) + go executeApprovedViaAPI(context.Background(), id, targetSlug, "pct_create:"+params) // see context.Background() comment above (apt_upgrade case) — same bug, same fix slog.Info("mcp: pct_create auto-approved via assent window", "execution_id", id) return textResult(fmt.Sprintf("pct_create on %s auto-approved via assent window — execution %s running. The LXC is being provisioned now.", targetSlug, id)), nil }