Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9cd38b5

Browse files
authored
feat!: remove the Tasks API from coderd and codersdk (#28789)
Removes the Tasks API and task orchestration from coderd and codersdk: the `/api/v2/tasks` handlers, task middleware and search filters, task name generation, autobuild pause/resume logic, task notification enqueues, telemetry, and the codersdk task types. Regenerates apidoc, the API reference docs, and `typesGenerated.ts`. **Breaking change:** the Tasks API is removed; requests to `/api/v2/tasks` fail. Tasks was deprecated in v2.34. **Stack context:** Coder Tasks removal stack (CODAGT-642): #28743 frontend → #28788 CLI → #28789 API → #28790 provisioner → #29084 notifications → #29085 build reasons → #29086 tables → #28750 permissions. The last four PRs replace the single data-drop migration that used to live in #28750; each carries one concern and its own migration number. **Why:** with the UI (#28743) and CLI (#28788) gone, this deletes the server surface. Database tables, RBAC, and notification templates stay until the top of the stack so every layer builds and migrates independently. **What to scrutinize:** - The `enable-ai-tasks` option is removed outright (review feedback): configs still carrying `enableAITasks` fail to parse until the key is deleted. - provisionerdserver now writes `HasAITask: false` on template/build flags; the column and these lines are dropped in #29085. - `codersdk.ResourceTypeTask` stays so audit logs recorded before the removal keep rendering. **Validation:** `go build ./...`, `go vet ./...`, `go test ./codersdk/...`, site biome and tsc, CLI goldens regenerated, pre-commit hooks. **Size:** +242/-10,792 over 76 files; additions are regenerated docs/goldens and test fixture updates. > Created by Xum (AI agent) on behalf of @ibetitsmike.
1 parent 2b89fb8 commit 9cd38b5

80 files changed

Lines changed: 224 additions & 11120 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cli/testdata/coder_list_--output_json.golden

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
"most_recently_seen": null
7272
},
7373
"template_version_preset_id": null,
74-
"has_ai_task": false,
7574
"has_external_agent": false
7675
},
7776
"latest_app_status": null,
@@ -90,7 +89,6 @@
9089
"allow_renames": false,
9190
"favorite": false,
9291
"next_start_at": "====[timestamp]=====",
93-
"is_prebuild": false,
94-
"task_id": null
92+
"is_prebuild": false
9593
}
9694
]

cli/testdata/server-config.yaml.golden

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -809,11 +809,6 @@ workspace_prebuilds:
809809
# limit; disabled when set to zero.
810810
# (default: 3, type: int)
811811
failure_hard_limit: 3
812-
# Enable Coder Tasks. When unset, the Tasks routes are not served, the Tasks UI
813-
# and its URLs are unavailable, the task RBAC permissions are stripped from
814-
# built-in roles, and the CLI task commands are hidden.
815-
# (default: false, type: bool)
816-
enableAITasks: false
817812
# Configure the background chat processing daemon.
818813
chat:
819814
# How many pending chats a worker should acquire per polling cycle.

coderd/agentapi/apps.go

Lines changed: 0 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,6 @@ func (a *AppsAPI) UpdateAppStatus(ctx context.Context, req *agentproto.UpdateApp
209209
}
210210
}
211211

212-
// Notify on state change to Working/Idle for AI tasks.
213-
a.enqueueAITaskStateNotification(ctx, app.ID, latestAppStatus, dbState)
214-
215212
if shouldBump(dbState, latestAppStatus) {
216213
// We pass time.Time{} for nextAutostart since we don't have access to
217214
// TemplateScheduleStore here. The activity bump logic handles this by
@@ -241,111 +238,3 @@ func shouldBump(dbState database.WorkspaceAppStatusState, latestAppStatus databa
241238
}
242239
return false
243240
}
244-
245-
// enqueueAITaskStateNotification enqueues a notification when an AI task's app
246-
// transitions to Working or Idle.
247-
// No-op if:
248-
// - the workspace agent app isn't configured as an AI task,
249-
// - the new state equals the latest persisted state,
250-
// - the workspace agent is not ready (still starting up).
251-
func (a *AppsAPI) enqueueAITaskStateNotification(
252-
ctx context.Context,
253-
appID uuid.UUID,
254-
latestAppStatus database.WorkspaceAppStatus,
255-
newAppStatus database.WorkspaceAppStatusState,
256-
) {
257-
var notificationTemplate uuid.UUID
258-
switch newAppStatus {
259-
case database.WorkspaceAppStatusStateWorking:
260-
notificationTemplate = notifications.TemplateTaskWorking
261-
case database.WorkspaceAppStatusStateIdle:
262-
notificationTemplate = notifications.TemplateTaskIdle
263-
case database.WorkspaceAppStatusStateComplete:
264-
notificationTemplate = notifications.TemplateTaskCompleted
265-
case database.WorkspaceAppStatusStateFailure:
266-
notificationTemplate = notifications.TemplateTaskFailed
267-
default:
268-
// Not a notifiable state, do nothing
269-
return
270-
}
271-
272-
taskID := a.Workspace.TaskID()
273-
if !taskID.Valid {
274-
// Workspace has no task ID, do nothing.
275-
return
276-
}
277-
278-
// Only fetch fresh agent state for task workspaces, since we need
279-
// the current lifecycle state to decide whether to send notifications.
280-
agent, err := a.AgentFn(ctx)
281-
if err != nil {
282-
a.Log.Warn(ctx, "failed to get agent for AI task notification", slog.Error(err))
283-
return
284-
}
285-
286-
// Only send notifications when the agent is ready. We want to skip
287-
// any state transitions that occur whilst the workspace is starting
288-
// up as it doesn't make sense to receive them.
289-
if agent.LifecycleState != database.WorkspaceAgentLifecycleStateReady {
290-
a.Log.Debug(ctx, "skipping AI task notification because agent is not ready",
291-
slog.F("agent_id", agent.ID),
292-
slog.F("lifecycle_state", agent.LifecycleState),
293-
slog.F("new_app_status", newAppStatus),
294-
)
295-
return
296-
}
297-
298-
task, err := a.Database.GetTaskByID(ctx, taskID.UUID)
299-
if err != nil {
300-
a.Log.Warn(ctx, "failed to get task", slog.Error(err))
301-
return
302-
}
303-
304-
if !task.WorkspaceAppID.Valid || task.WorkspaceAppID.UUID != appID {
305-
// Non-task app, do nothing.
306-
return
307-
}
308-
309-
// Skip if the latest persisted state equals the new state (no new transition)
310-
// Note: uuid.Nil check is valid here. If no previous status exists,
311-
// GetLatestWorkspaceAppStatusByAppID returns sql.ErrNoRows and we get a zero-value struct.
312-
if latestAppStatus.ID != uuid.Nil && latestAppStatus.State == newAppStatus {
313-
return
314-
}
315-
316-
// Skip the initial "Working" notification when the task first starts.
317-
// This is obvious to the user since they just created the task.
318-
// We still notify on the first "Idle" status and all subsequent transitions.
319-
if latestAppStatus.ID == uuid.Nil && newAppStatus == database.WorkspaceAppStatusStateWorking {
320-
return
321-
}
322-
323-
ws, ok := a.Workspace.AsWorkspaceIdentity()
324-
if !ok {
325-
a.Log.Warn(ctx, "failed to get workspace identity for AI task notification")
326-
return
327-
}
328-
329-
if _, err := a.NotificationsEnqueuer.EnqueueWithData(
330-
// nolint:gocritic // Need notifier actor to enqueue notifications
331-
dbauthz.AsNotifier(ctx),
332-
ws.OwnerID,
333-
notificationTemplate,
334-
map[string]string{
335-
"task": task.Name,
336-
"workspace": ws.Name,
337-
},
338-
map[string]any{
339-
// Use a 1-minute bucketed timestamp to bypass per-day dedupe,
340-
// allowing identical content to resend within the same day
341-
// (but not more than once every 10s).
342-
"dedupe_bypass_ts": a.Clock.Now().UTC().Truncate(time.Minute),
343-
},
344-
"api-workspace-agent-app-status",
345-
// Associate this notification with related entities
346-
ws.ID, ws.OwnerID, ws.OrganizationID, appID,
347-
); err != nil {
348-
a.Log.Warn(ctx, "failed to notify of task state", slog.Error(err))
349-
return
350-
}
351-
}

coderd/agentapi/apps_test.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"github.com/coder/coder/v2/coderd/agentapi"
1717
"github.com/coder/coder/v2/coderd/database"
1818
"github.com/coder/coder/v2/coderd/database/dbmock"
19-
"github.com/coder/coder/v2/coderd/notifications"
2019
"github.com/coder/coder/v2/coderd/notifications/notificationstest"
2120
"github.com/coder/coder/v2/coderd/wspubsub"
2221
"github.com/coder/coder/v2/codersdk"
@@ -269,10 +268,6 @@ func TestWorkspaceAgentAppStatus(t *testing.T) {
269268

270269
workspace := database.Workspace{
271270
ID: uuid.UUID{9},
272-
TaskID: uuid.NullUUID{
273-
Valid: true,
274-
UUID: uuid.UUID{7},
275-
},
276271
}
277272
cachedWs := &agentapi.CachedWorkspaceFields{}
278273
cachedWs.UpdateValues(workspace)
@@ -301,14 +296,6 @@ func TestWorkspaceAgentAppStatus(t *testing.T) {
301296
AgentID: agent.ID,
302297
Slug: "vscode",
303298
}).Times(1).Return(app, nil)
304-
task := database.Task{
305-
ID: uuid.UUID{7},
306-
WorkspaceAppID: uuid.NullUUID{
307-
Valid: true,
308-
UUID: app.ID,
309-
},
310-
}
311-
mDB.EXPECT().GetTaskByID(gomock.Any(), task.ID).Times(1).Return(task, nil)
312299
appStatus := database.WorkspaceAppStatus{
313300
ID: uuid.UUID{6},
314301
}
@@ -336,8 +323,6 @@ func TestWorkspaceAgentAppStatus(t *testing.T) {
336323

337324
kind := testutil.RequireReceive(ctx, t, workspaceUpdates)
338325
require.Equal(t, wspubsub.WorkspaceEventKindAgentAppStatusUpdate, kind)
339-
sent := fEnq.Sent(notificationstest.WithTemplateID(notifications.TemplateTaskCompleted))
340-
require.Len(t, sent, 1)
341326
})
342327

343328
t.Run("FailUnknownApp", func(t *testing.T) {

coderd/agentapi/cached_workspace.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"sync"
66

7-
"github.com/google/uuid"
87
"golang.org/x/xerrors"
98

109
"github.com/coder/coder/v2/coderd/database"
@@ -24,14 +23,12 @@ type CachedWorkspaceFields struct {
2423
lock sync.RWMutex
2524

2625
identity database.WorkspaceIdentity
27-
taskID uuid.NullUUID
2826
}
2927

3028
func (cws *CachedWorkspaceFields) Clear() {
3129
cws.lock.Lock()
3230
defer cws.lock.Unlock()
3331
cws.identity = database.WorkspaceIdentity{}
34-
cws.taskID = uuid.NullUUID{}
3532
}
3633

3734
func (cws *CachedWorkspaceFields) UpdateValues(ws database.Workspace) {
@@ -45,13 +42,6 @@ func (cws *CachedWorkspaceFields) UpdateValues(ws database.Workspace) {
4542
cws.identity.OwnerUsername = ws.OwnerUsername
4643
cws.identity.TemplateName = ws.TemplateName
4744
cws.identity.AutostartSchedule = ws.AutostartSchedule
48-
cws.taskID = ws.TaskID
49-
}
50-
51-
func (cws *CachedWorkspaceFields) TaskID() uuid.NullUUID {
52-
cws.lock.RLock()
53-
defer cws.lock.RUnlock()
54-
return cws.taskID
5545
}
5646

5747
// Returns the Workspace, true, unless the workspace has not been cached (nuked or was a prebuild).

coderd/aiseats/aiseats.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ func ReasonAIBridge(description string) Reason {
2020
return Reason{EventType: database.AISeatUsageReasonAibridge, Description: description}
2121
}
2222

23-
// ReasonTask constructs a reason for usage originating from tasks.
24-
func ReasonTask(description string) Reason {
25-
return Reason{EventType: database.AISeatUsageReasonTask, Description: description}
26-
}
27-
2823
// SeatTracker records AI seat consumption state.
2924
type SeatTracker interface {
3025
// RecordUsage does not return an error to prevent blocking the user from using

0 commit comments

Comments
 (0)