Compare commits

...

1 Commits

Author SHA1 Message Date
Jiang Bohan
40f594e7b3 fix(server): allow deleting runtimes when all bound agents are archived
Previously, runtimes could never be deleted once an agent was created
because agents can only be archived (not deleted) and the count check
included archived agents. Now the check only counts active agents, and
archived agents are cleaned up before runtime deletion.
2026-04-09 19:15:50 +08:00
3 changed files with 28 additions and 10 deletions

View File

@@ -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
}

View File

@@ -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'

View File

@@ -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;