Skip to content

Commit d92df3e

Browse files
committed
chore(coderd/provisionerdserver): avoid fk error on invalid ai_task_sidebar_app_id
1 parent bdde982 commit d92df3e

File tree

2 files changed

+65
-5
lines changed

2 files changed

+65
-5
lines changed

coderd/provisionerdserver/provisionerdserver.go

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1925,12 +1925,16 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
19251925
return xerrors.Errorf("update workspace build deadline: %w", err)
19261926
}
19271927

1928+
appIDs := make([]string, 0)
19281929
agentTimeouts := make(map[time.Duration]bool) // A set of agent timeouts.
19291930
// This could be a bulk insert to improve performance.
19301931
for _, protoResource := range jobType.WorkspaceBuild.Resources {
19311932
for _, protoAgent := range protoResource.Agents {
19321933
dur := time.Duration(protoAgent.GetConnectionTimeoutSeconds()) * time.Second
19331934
agentTimeouts[dur] = true
1935+
for _, app := range protoAgent.GetApps() {
1936+
appIDs = append(appIDs, app.GetId())
1937+
}
19341938
}
19351939

19361940
err = InsertWorkspaceResource(ctx, db, job.ID, workspaceBuild.Transition, protoResource, telemetrySnapshot)
@@ -1946,10 +1950,15 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
19461950

19471951
var sidebarAppID uuid.NullUUID
19481952
hasAITask := len(jobType.WorkspaceBuild.AiTasks) == 1
1953+
warnUnknownSidebarAppID := false
19491954
if hasAITask {
19501955
task := jobType.WorkspaceBuild.AiTasks[0]
1951-
if task.SidebarApp == nil {
1952-
return xerrors.Errorf("update ai task: sidebar app is nil")
1956+
if task.SidebarApp == nil || len(task.SidebarApp.Id) == 0 {
1957+
return xerrors.Errorf("update ai task: sidebar app is nil or empty")
1958+
}
1959+
1960+
if !slices.Contains(appIDs, task.SidebarApp.Id) {
1961+
warnUnknownSidebarAppID = true
19531962
}
19541963

19551964
id, err := uuid.Parse(task.SidebarApp.Id)
@@ -1960,18 +1969,53 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
19601969
sidebarAppID = uuid.NullUUID{UUID: id, Valid: true}
19611970
}
19621971

1972+
if warnUnknownSidebarAppID {
1973+
// Ref: https://github.com/coder/coder/issues/18776
1974+
// This can happen for a number of reasons:
1975+
// 1. Misconfigured template
1976+
// 2. Count=0 on the agent due to stop transition, meaning the associated coder_app was not inserted.
1977+
// Failing the build at this point is not ideal, so log a warning instead.
1978+
s.Logger.Warn(ctx, "unknown ai_task_sidebar_app_id",
1979+
slog.F("ai_task_sidebar_app_id", sidebarAppID.UUID.String()),
1980+
slog.F("job_id", job.ID.String()),
1981+
slog.F("workspace_id", workspace.ID),
1982+
slog.F("workspace_build_id", workspaceBuild.ID),
1983+
slog.F("transition", string(workspaceBuild.Transition)),
1984+
)
1985+
// In order to surface this to the user, we will also insert a warning into the build logs.
1986+
if _, err := db.InsertProvisionerJobLogs(ctx, database.InsertProvisionerJobLogsParams{
1987+
JobID: jobID,
1988+
CreatedAt: []time.Time{now},
1989+
Source: []database.LogSource{database.LogSourceProvisionerDaemon},
1990+
Level: []database.LogLevel{database.LogLevelWarn},
1991+
Stage: []string{"Cleaning Up"},
1992+
Output: []string{
1993+
fmt.Sprintf("Unknown ai_task_sidebar_app_id %q. This workspace will be unable to run AI tasks. This may be due to a template configuration issue, please check with the template author.", sidebarAppID.UUID.String()),
1994+
},
1995+
}); err != nil {
1996+
s.Logger.Error(ctx, "insert provisioner job log for ai task sidebar app id warning",
1997+
slog.F("job_id", jobID),
1998+
slog.F("workspace_id", workspace.ID),
1999+
slog.F("workspace_build_id", workspaceBuild.ID),
2000+
slog.F("transition", string(workspaceBuild.Transition)),
2001+
)
2002+
}
2003+
// Important: reset hasAITask and sidebarAppID so that we don't run into a fk constraint violation.
2004+
hasAITask = false
2005+
sidebarAppID = uuid.NullUUID{}
2006+
}
2007+
19632008
// Regardless of whether there is an AI task or not, update the field to indicate one way or the other since it
19642009
// always defaults to nil. ONLY if has_ai_task=true MUST ai_task_sidebar_app_id be set.
1965-
err = db.UpdateWorkspaceBuildAITaskByID(ctx, database.UpdateWorkspaceBuildAITaskByIDParams{
2010+
if err := db.UpdateWorkspaceBuildAITaskByID(ctx, database.UpdateWorkspaceBuildAITaskByIDParams{
19662011
ID: workspaceBuild.ID,
19672012
HasAITask: sql.NullBool{
19682013
Bool: hasAITask,
19692014
Valid: true,
19702015
},
19712016
SidebarAppID: sidebarAppID,
19722017
UpdatedAt: now,
1973-
})
1974-
if err != nil {
2018+
}); err != nil {
19752019
return xerrors.Errorf("update workspace build ai tasks flag: %w", err)
19762020
}
19772021

coderd/provisionerdserver/provisionerdserver_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2794,6 +2794,22 @@ func TestCompleteJob(t *testing.T) {
27942794
},
27952795
expected: true,
27962796
},
2797+
// Checks regression for https://github.com/coder/coder/issues/18776
2798+
{
2799+
name: "non-existing app",
2800+
input: &proto.CompletedJob_WorkspaceBuild{
2801+
AiTasks: []*sdkproto.AITask{
2802+
{
2803+
Id: uuid.NewString(),
2804+
SidebarApp: &sdkproto.AITaskSidebarApp{
2805+
// Non-existing app ID would previously trigger a FK violation.
2806+
Id: uuid.NewString(),
2807+
},
2808+
},
2809+
},
2810+
},
2811+
expected: false,
2812+
},
27972813
} {
27982814
t.Run(tc.name, func(t *testing.T) {
27992815
t.Parallel()

0 commit comments

Comments
 (0)