From c3224b793e96c18adb3c5690a62483f1cf78597c Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 11 Dec 2025 13:04:13 +0000 Subject: [PATCH] fix: handle scenario where provisionerdserver deletes task before coderd (#21220) --- coderd/aitasks.go | 8 +++++++- coderd/provisionerdserver/provisionerdserver.go | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/coderd/aitasks.go b/coderd/aitasks.go index 2313ee745f..5192f1fb96 100644 --- a/coderd/aitasks.go +++ b/coderd/aitasks.go @@ -2,6 +2,8 @@ package coderd import ( "context" + "database/sql" + "errors" "fmt" "net" "net/http" @@ -622,11 +624,15 @@ func (api *API) taskDelete(rw http.ResponseWriter, r *http.Request) { } } + // As an implementation detail of the workspace build transition, we also delete + // the associated task. This means that we have a race between provisionerdserver + // and here with deleting the task. In a real world scenario we'll never lose the + // race but we should still handle it anyways. _, err := api.Database.DeleteTask(ctx, database.DeleteTaskParams{ ID: task.ID, DeletedAt: dbtime.Time(now), }) - if err != nil { + if err != nil && !errors.Is(err, sql.ErrNoRows) { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to delete task", Detail: err.Error(), diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index c4598beaf8..8cc84e6a45 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -2267,6 +2267,13 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro if err != nil { return xerrors.Errorf("update workspace deleted: %w", err) } + + // A user might delete their task workspace directly, instead of + // deleting the task. To avoid leaving the Task in a scenario where + // it has no workspace, we also attempt to delete the task. + // + // Deleting the task may fail if it has already been deleted as part + // of the typical task deletion workflow, so we explicitly allow that. if workspace.TaskID.Valid { if _, err := db.DeleteTask(ctx, database.DeleteTaskParams{ ID: workspace.TaskID.UUID,