diff --git a/server/internal/handler/runtime.go b/server/internal/handler/runtime.go index 2cfc44cd3..90b9d8429 100644 --- a/server/internal/handler/runtime.go +++ b/server/internal/handler/runtime.go @@ -343,14 +343,20 @@ func (h *Handler) DeleteAgentRuntime(w http.ResponseWriter, r *http.Request) { return } - // Check if any agents are bound to this runtime (ON DELETE RESTRICT). - agentCount, err := h.Queries.CountAgentsByRuntime(r.Context(), rt.ID) + // Check if any active (non-archived) agents are bound to this runtime. + activeCount, err := h.Queries.CountActiveAgentsByRuntime(r.Context(), rt.ID) if err != nil { writeError(w, http.StatusInternalServerError, "failed to check runtime dependencies") return } - if agentCount > 0 { - writeError(w, http.StatusConflict, "cannot delete runtime: it has agents bound to it. Reassign or remove the agents first.") + if activeCount > 0 { + writeError(w, http.StatusConflict, "cannot delete runtime: it has active agents bound to it. Archive or reassign the agents first.") + return + } + + // Remove archived agents so the FK constraint (ON DELETE RESTRICT) won't block deletion. + if err := h.Queries.DeleteArchivedAgentsByRuntime(r.Context(), rt.ID); err != nil { + writeError(w, http.StatusInternalServerError, "failed to clean up archived agents") return } diff --git a/server/pkg/db/generated/runtime.sql.go b/server/pkg/db/generated/runtime.sql.go index e605fc1ec..683d97b8d 100644 --- a/server/pkg/db/generated/runtime.sql.go +++ b/server/pkg/db/generated/runtime.sql.go @@ -11,12 +11,12 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) -const countAgentsByRuntime = `-- name: CountAgentsByRuntime :one -SELECT count(*) FROM agent WHERE runtime_id = $1 +const countActiveAgentsByRuntime = `-- name: CountActiveAgentsByRuntime :one +SELECT count(*) FROM agent WHERE runtime_id = $1 AND archived_at IS NULL ` -func (q *Queries) CountAgentsByRuntime(ctx context.Context, runtimeID pgtype.UUID) (int64, error) { - row := q.db.QueryRow(ctx, countAgentsByRuntime, runtimeID) +func (q *Queries) CountActiveAgentsByRuntime(ctx context.Context, runtimeID pgtype.UUID) (int64, error) { + row := q.db.QueryRow(ctx, countActiveAgentsByRuntime, runtimeID) var count int64 err := row.Scan(&count) return count, err @@ -31,6 +31,15 @@ func (q *Queries) DeleteAgentRuntime(ctx context.Context, id pgtype.UUID) error return err } +const deleteArchivedAgentsByRuntime = `-- name: DeleteArchivedAgentsByRuntime :exec +DELETE FROM agent WHERE runtime_id = $1 AND archived_at IS NOT NULL +` + +func (q *Queries) DeleteArchivedAgentsByRuntime(ctx context.Context, runtimeID pgtype.UUID) error { + _, err := q.db.Exec(ctx, deleteArchivedAgentsByRuntime, runtimeID) + return err +} + const failTasksForOfflineRuntimes = `-- name: FailTasksForOfflineRuntimes :many UPDATE agent_task_queue SET status = 'failed', completed_at = now(), error = 'runtime went offline' diff --git a/server/pkg/db/queries/runtime.sql b/server/pkg/db/queries/runtime.sql index 1b80eb354..1e70a5372 100644 --- a/server/pkg/db/queries/runtime.sql +++ b/server/pkg/db/queries/runtime.sql @@ -73,5 +73,8 @@ ORDER BY created_at ASC; -- name: DeleteAgentRuntime :exec DELETE FROM agent_runtime WHERE id = $1; --- name: CountAgentsByRuntime :one -SELECT count(*) FROM agent WHERE runtime_id = $1; +-- name: CountActiveAgentsByRuntime :one +SELECT count(*) FROM agent WHERE runtime_id = $1 AND archived_at IS NULL; + +-- name: DeleteArchivedAgentsByRuntime :exec +DELETE FROM agent WHERE runtime_id = $1 AND archived_at IS NOT NULL;