From 3648ccec5081477717a618569110064ebf26419d Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:27:06 +0000 Subject: [PATCH 1/3] fix(coderd): show correct deletion time in dormancy notification Code-only backport of #26488 to release/2.34. The stored notification body is unchanged; the timeTilDormant label now carries the auto-delete countdown from the workspace's deleting_at instead of the dormancy threshold. When auto-delete is disabled the label falls back to generic wording so the body no longer promises a deletion that will not happen. (cherry picked from #26488, without migration 000527) --- coderd/autobuild/lifecycle_executor.go | 16 +++- coderd/autobuild/lifecycle_executor_test.go | 88 +++++++++++++++++++++ coderd/workspaces.go | 24 +++--- coderd/workspaces_test.go | 69 ++++++++++++++++ 4 files changed, 180 insertions(+), 17 deletions(-) diff --git a/coderd/autobuild/lifecycle_executor.go b/coderd/autobuild/lifecycle_executor.go index 60954373c470f..ddafdea16a360 100644 --- a/coderd/autobuild/lifecycle_executor.go +++ b/coderd/autobuild/lifecycle_executor.go @@ -382,8 +382,11 @@ func (e *Executor) runOnce(t time.Time) Stats { Old: wsOld.WorkspaceTable(), New: wsNew, } - // To keep the `ws` accurate without doing a sql fetch + // To keep the `ws` accurate without doing a sql fetch. + // deleting_at is computed by the UPDATE from the template's + // time_til_dormant_autodelete. ws.DormantAt = wsNew.DormantAt + ws.DeletingAt = wsNew.DeletingAt shouldNotifyDormancy = true @@ -467,7 +470,14 @@ func (e *Executor) runOnce(t time.Time) Stats { } } if shouldNotifyDormancy { - dormantTime := dbtime.Now().Add(time.Duration(tmpl.TimeTilDormant)) + // The notification body renders this label inside the + // "will be automatically deleted in ..." sentence, so it + // must carry the auto-delete countdown. When auto-delete is + // disabled there is no deadline, so use generic wording. + timeTilDelete := "line with your template's auto-deletion policy" + if ws.DeletingAt.Valid { + timeTilDelete = humanize.Time(ws.DeletingAt.Time) + } _, err = e.notificationsEnqueuer.Enqueue( e.ctx, ws.OwnerID, @@ -475,7 +485,7 @@ func (e *Executor) runOnce(t time.Time) Stats { map[string]string{ "name": ws.Name, "reason": "inactivity exceeded the dormancy threshold", - "timeTilDormant": humanize.Time(dormantTime), + "timeTilDormant": timeTilDelete, }, "lifecycle_executor", ws.ID, diff --git a/coderd/autobuild/lifecycle_executor_test.go b/coderd/autobuild/lifecycle_executor_test.go index 345647977d663..10620be5a3394 100644 --- a/coderd/autobuild/lifecycle_executor_test.go +++ b/coderd/autobuild/lifecycle_executor_test.go @@ -1336,6 +1336,94 @@ func TestNotifications(t *testing.T) { require.Contains(t, sent[0].Targets, workspace.ID) require.Contains(t, sent[0].Targets, workspace.OrganizationID) require.Contains(t, sent[0].Targets, workspace.OwnerID) + + // Auto-delete is not configured, so the label must fall back to + // generic wording instead of promising a deletion time. + require.Equal(t, "line with your template's auto-deletion policy", sent[0].Labels["timeTilDormant"]) + require.Equal(t, workspace.Name, sent[0].Labels["name"]) + require.Equal(t, "inactivity exceeded the dormancy threshold", sent[0].Labels["reason"]) + }) + + t.Run("DormancyAutoDelete", func(t *testing.T) { + t.Parallel() + + // Setup template with dormancy and auto-delete and create a workspace + // with it. The two durations are intentionally far apart to reliably + // check what's rendered in the notification. + var ( + ticker = make(chan time.Time) + statCh = make(chan autobuild.Stats) + notifyEnq = notificationstest.FakeEnqueuer{} + // 35 days is inside humanize.Time's "1 month" bucket (between 30 and 60 days). + timeTilDormant = time.Minute + timeTilDormantAutoDelete = 35 * 24 * time.Hour + client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{ + AutobuildTicker: ticker, + AutobuildStats: statCh, + IncludeProvisionerDaemon: true, + NotificationsEnqueuer: ¬ifyEnq, + TemplateScheduleStore: schedule.MockTemplateScheduleStore{ + SetFn: func(ctx context.Context, db database.Store, template database.Template, options schedule.TemplateScheduleOptions) (database.Template, error) { + template.TimeTilDormant = int64(options.TimeTilDormant) + template.TimeTilDormantAutoDelete = int64(options.TimeTilDormantAutoDelete) + return schedule.NewAGPLTemplateScheduleStore().Set(ctx, db, template, options) + }, + GetFn: func(_ context.Context, _ database.Store, _ uuid.UUID) (schedule.TemplateScheduleOptions, error) { + return schedule.TemplateScheduleOptions{ + UserAutostartEnabled: false, + UserAutostopEnabled: true, + DefaultTTL: 0, + AutostopRequirement: schedule.TemplateAutostopRequirement{}, + TimeTilDormant: timeTilDormant, + TimeTilDormantAutoDelete: timeTilDormantAutoDelete, + }, nil + }, + }, + }) + admin = coderdtest.CreateFirstUser(t, client) + version = coderdtest.CreateTemplateVersion(t, client, admin.OrganizationID, nil) + ) + + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, admin.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { + ctr.TimeTilDormantMillis = ptr.Ref(timeTilDormant.Milliseconds()) + ctr.TimeTilDormantAutoDeleteMillis = ptr.Ref(timeTilDormantAutoDelete.Milliseconds()) + }) + userClient, _ := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID) + workspace := coderdtest.CreateWorkspace(t, userClient, template.ID) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, userClient, workspace.LatestBuild.ID) + + // Stop workspace + workspace = coderdtest.MustTransitionWorkspace(t, client, workspace.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionStop) + _ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, userClient, workspace.LatestBuild.ID) + + p, err := coderdtest.GetProvisionerForTags(db, time.Now(), workspace.OrganizationID, nil) + require.NoError(t, err) + + // Wait for workspace to become dormant + notifyEnq.Clear() + tickTime := workspace.LastUsedAt.Add(timeTilDormant * 3) + coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, tickTime) + ticker <- tickTime + _ = testutil.TryReceive(testutil.Context(t, testutil.WaitShort), t, statCh) + + // Check that the workspace is dormant + workspace = coderdtest.MustWorkspace(t, client, workspace.ID) + require.NotNil(t, workspace.DormantAt) + + // The label must render the deletion countdown from the template's + // `time_til_dormant_autodelete` value. With auto-delete at 35 days and + // dormancy at 1 minute, humanize.Time renders it as "1 month from now". + sent := notifyEnq.Sent() + require.Len(t, sent, 1) + require.Equal(t, sent[0].TemplateID, notifications.TemplateWorkspaceDormant) + require.Contains(t, sent[0].Labels, "timeTilDormant") + require.Contains(t, sent[0].Labels["timeTilDormant"], "1 month", + "timeTilDormant must humanize TimeTilDormantAutoDelete, got %q", + sent[0].Labels["timeTilDormant"]) + require.NotContains(t, sent[0].Labels["timeTilDormant"], "ago", + "timeTilDormant must be a future timestamp, got %q", + sent[0].Labels["timeTilDormant"]) }) } diff --git a/coderd/workspaces.go b/coderd/workspaces.go index b812011edca75..d76ce5df9df47 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1514,19 +1514,15 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) { ) } - tmpl, tmplErr := api.Database.GetTemplateByID(ctx, newWorkspace.TemplateID) - if tmplErr != nil { - api.Logger.Warn( - ctx, - "failed to fetch the template of the workspace marked as dormant", - slog.Error(err), - slog.F("workspace_id", newWorkspace.ID), - slog.F("template_id", newWorkspace.TemplateID), - ) - } - - if initiatorErr == nil && tmplErr == nil { - dormantTime := dbtime.Time(now).Add(time.Duration(tmpl.TimeTilDormant)) + if initiatorErr == nil { + // The notification body renders this label inside the + // "will be automatically deleted in ..." sentence, so it must + // carry the auto-delete countdown. When auto-delete is disabled + // there is no deadline, so use generic wording. + timeTilDelete := "line with your template's auto-deletion policy" + if newWorkspace.DeletingAt.Valid { + timeTilDelete = humanize.Time(newWorkspace.DeletingAt.Time) + } _, err = api.NotificationsEnqueuer.Enqueue( // nolint:gocritic // Need notifier actor to enqueue notifications dbauthz.AsNotifier(ctx), @@ -1535,7 +1531,7 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) { map[string]string{ "name": newWorkspace.Name, "reason": "a " + initiator.Username + " request", - "timeTilDormant": humanize.Time(dormantTime), + "timeTilDormant": timeTilDelete, }, "api", newWorkspace.ID, diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index de7ae93ccf6fa..be1561c3c8a48 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -5156,6 +5156,75 @@ func TestWorkspaceNotifications(t *testing.T) { require.Contains(t, sent[0].Targets, workspace.ID) require.Contains(t, sent[0].Targets, workspace.OrganizationID) require.Contains(t, sent[0].Targets, workspace.OwnerID) + // Auto-delete is not configured, so the label must fall back to + // generic wording instead of promising a deletion time. + require.Equal(t, "line with your template's auto-deletion policy", sent[0].Labels["timeTilDormant"]) + }) + + t.Run("InitiatorNotOwnerWithAutoDelete", func(t *testing.T) { + t.Parallel() + + // Given + var ( + notifyEnq = ¬ificationstest.FakeEnqueuer{} + // 35 days sits solidly inside humanize.Time's "1 month" + // bucket (between 30 and 60 days), so the rendered label is + // deterministic regardless of microsecond-level timing + // differences. + timeTilDormantAutoDelete = 35 * 24 * time.Hour + client = coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + NotificationsEnqueuer: notifyEnq, + // AGPL templateScheduleStore drops TimeTilDormantAutoDelete + // when Set runs. The mock propagates it into the template + // row so the UPDATE in UpdateWorkspaceDormantDeletingAt + // can compute deleting_at. + TemplateScheduleStore: schedule.MockTemplateScheduleStore{ + SetFn: func(ctx context.Context, db database.Store, template database.Template, options schedule.TemplateScheduleOptions) (database.Template, error) { + template.TimeTilDormantAutoDelete = int64(options.TimeTilDormantAutoDelete) + return schedule.NewAGPLTemplateScheduleStore().Set(ctx, db, template, options) + }, + GetFn: func(_ context.Context, _ database.Store, _ uuid.UUID) (schedule.TemplateScheduleOptions, error) { + return schedule.TemplateScheduleOptions{ + UserAutostartEnabled: false, + UserAutostopEnabled: true, + DefaultTTL: 0, + AutostopRequirement: schedule.TemplateAutostopRequirement{}, + TimeTilDormantAutoDelete: timeTilDormantAutoDelete, + }, nil + }, + }, + }) + user = coderdtest.CreateFirstUser(t, client) + memberClient, _ = coderdtest.CreateAnotherUser(t, client, user.OrganizationID, rbac.RoleOwner()) + version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + _ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + template = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { + ctr.TimeTilDormantAutoDeleteMillis = ptr.Ref[int64](timeTilDormantAutoDelete.Milliseconds()) + }) + workspace = coderdtest.CreateWorkspace(t, client, template.ID) + _ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) + ) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + t.Cleanup(cancel) + + // When + err := memberClient.UpdateWorkspaceDormancy(ctx, workspace.ID, codersdk.UpdateWorkspaceDormancy{ + Dormant: true, + }) + + // Then + require.NoError(t, err, "mark workspace as dormant") + sent := notifyEnq.Sent(notificationstest.WithTemplateID(notifications.TemplateWorkspaceDormant)) + require.Len(t, sent, 1) + require.Contains(t, sent[0].Labels, "timeTilDormant") + require.Contains(t, sent[0].Labels["timeTilDormant"], "1 month", + "timeTilDormant must humanize the workspace's deleting_at, got %q", + sent[0].Labels["timeTilDormant"]) + require.NotContains(t, sent[0].Labels["timeTilDormant"], "ago", + "timeTilDormant must be a future timestamp, got %q", + sent[0].Labels["timeTilDormant"]) }) t.Run("InitiatorIsOwner", func(t *testing.T) { From 6482d8a957e2b0372477fb285f324415f2ab572f Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:25:10 +0000 Subject: [PATCH 2/3] chore(coderd): tighten dormancy notification comments --- coderd/autobuild/lifecycle_executor.go | 12 +++++------- coderd/autobuild/lifecycle_executor_test.go | 13 +------------ coderd/workspaces.go | 7 +++---- coderd/workspaces_test.go | 13 +++---------- 4 files changed, 12 insertions(+), 33 deletions(-) diff --git a/coderd/autobuild/lifecycle_executor.go b/coderd/autobuild/lifecycle_executor.go index ddafdea16a360..f6f84c9912b3d 100644 --- a/coderd/autobuild/lifecycle_executor.go +++ b/coderd/autobuild/lifecycle_executor.go @@ -382,9 +382,8 @@ func (e *Executor) runOnce(t time.Time) Stats { Old: wsOld.WorkspaceTable(), New: wsNew, } - // To keep the `ws` accurate without doing a sql fetch. - // deleting_at is computed by the UPDATE from the template's - // time_til_dormant_autodelete. + // Keep `ws` accurate without a sql fetch. The UPDATE derives + // deleting_at from the template's time_til_dormant_autodelete. ws.DormantAt = wsNew.DormantAt ws.DeletingAt = wsNew.DeletingAt @@ -470,10 +469,9 @@ func (e *Executor) runOnce(t time.Time) Stats { } } if shouldNotifyDormancy { - // The notification body renders this label inside the - // "will be automatically deleted in ..." sentence, so it - // must carry the auto-delete countdown. When auto-delete is - // disabled there is no deadline, so use generic wording. + // The body renders this label in its "will be automatically + // deleted in ..." sentence, so it must carry the auto-delete + // countdown, or generic wording when there is no deadline. timeTilDelete := "line with your template's auto-deletion policy" if ws.DeletingAt.Valid { timeTilDelete = humanize.Time(ws.DeletingAt.Time) diff --git a/coderd/autobuild/lifecycle_executor_test.go b/coderd/autobuild/lifecycle_executor_test.go index 10620be5a3394..f3126dc2f712a 100644 --- a/coderd/autobuild/lifecycle_executor_test.go +++ b/coderd/autobuild/lifecycle_executor_test.go @@ -1337,8 +1337,6 @@ func TestNotifications(t *testing.T) { require.Contains(t, sent[0].Targets, workspace.OrganizationID) require.Contains(t, sent[0].Targets, workspace.OwnerID) - // Auto-delete is not configured, so the label must fall back to - // generic wording instead of promising a deletion time. require.Equal(t, "line with your template's auto-deletion policy", sent[0].Labels["timeTilDormant"]) require.Equal(t, workspace.Name, sent[0].Labels["name"]) require.Equal(t, "inactivity exceeded the dormancy threshold", sent[0].Labels["reason"]) @@ -1347,14 +1345,11 @@ func TestNotifications(t *testing.T) { t.Run("DormancyAutoDelete", func(t *testing.T) { t.Parallel() - // Setup template with dormancy and auto-delete and create a workspace - // with it. The two durations are intentionally far apart to reliably - // check what's rendered in the notification. var ( ticker = make(chan time.Time) statCh = make(chan autobuild.Stats) notifyEnq = notificationstest.FakeEnqueuer{} - // 35 days is inside humanize.Time's "1 month" bucket (between 30 and 60 days). + // 35 days keeps humanize.Time deterministically in its "1 month" bucket. timeTilDormant = time.Minute timeTilDormantAutoDelete = 35 * 24 * time.Hour client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{ @@ -1393,27 +1388,21 @@ func TestNotifications(t *testing.T) { workspace := coderdtest.CreateWorkspace(t, userClient, template.ID) coderdtest.AwaitWorkspaceBuildJobCompleted(t, userClient, workspace.LatestBuild.ID) - // Stop workspace workspace = coderdtest.MustTransitionWorkspace(t, client, workspace.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionStop) _ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, userClient, workspace.LatestBuild.ID) p, err := coderdtest.GetProvisionerForTags(db, time.Now(), workspace.OrganizationID, nil) require.NoError(t, err) - // Wait for workspace to become dormant notifyEnq.Clear() tickTime := workspace.LastUsedAt.Add(timeTilDormant * 3) coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, tickTime) ticker <- tickTime _ = testutil.TryReceive(testutil.Context(t, testutil.WaitShort), t, statCh) - // Check that the workspace is dormant workspace = coderdtest.MustWorkspace(t, client, workspace.ID) require.NotNil(t, workspace.DormantAt) - // The label must render the deletion countdown from the template's - // `time_til_dormant_autodelete` value. With auto-delete at 35 days and - // dormancy at 1 minute, humanize.Time renders it as "1 month from now". sent := notifyEnq.Sent() require.Len(t, sent, 1) require.Equal(t, sent[0].TemplateID, notifications.TemplateWorkspaceDormant) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index d76ce5df9df47..a302a12ac2356 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1515,10 +1515,9 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) { } if initiatorErr == nil { - // The notification body renders this label inside the - // "will be automatically deleted in ..." sentence, so it must - // carry the auto-delete countdown. When auto-delete is disabled - // there is no deadline, so use generic wording. + // The body renders this label in its "will be automatically + // deleted in ..." sentence, so it must carry the auto-delete + // countdown, or generic wording when there is no deadline. timeTilDelete := "line with your template's auto-deletion policy" if newWorkspace.DeletingAt.Valid { timeTilDelete = humanize.Time(newWorkspace.DeletingAt.Time) diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index be1561c3c8a48..b2fdbd705081a 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -5156,8 +5156,6 @@ func TestWorkspaceNotifications(t *testing.T) { require.Contains(t, sent[0].Targets, workspace.ID) require.Contains(t, sent[0].Targets, workspace.OrganizationID) require.Contains(t, sent[0].Targets, workspace.OwnerID) - // Auto-delete is not configured, so the label must fall back to - // generic wording instead of promising a deletion time. require.Equal(t, "line with your template's auto-deletion policy", sent[0].Labels["timeTilDormant"]) }) @@ -5167,18 +5165,13 @@ func TestWorkspaceNotifications(t *testing.T) { // Given var ( notifyEnq = ¬ificationstest.FakeEnqueuer{} - // 35 days sits solidly inside humanize.Time's "1 month" - // bucket (between 30 and 60 days), so the rendered label is - // deterministic regardless of microsecond-level timing - // differences. + // 35 days keeps humanize.Time deterministically in its "1 month" bucket. timeTilDormantAutoDelete = 35 * 24 * time.Hour client = coderdtest.New(t, &coderdtest.Options{ IncludeProvisionerDaemon: true, NotificationsEnqueuer: notifyEnq, - // AGPL templateScheduleStore drops TimeTilDormantAutoDelete - // when Set runs. The mock propagates it into the template - // row so the UPDATE in UpdateWorkspaceDormantDeletingAt - // can compute deleting_at. + // The AGPL store ignores TimeTilDormantAutoDelete, so the mock + // writes it to the template row for deleting_at computation. TemplateScheduleStore: schedule.MockTemplateScheduleStore{ SetFn: func(ctx context.Context, db database.Store, template database.Template, options schedule.TemplateScheduleOptions) (database.Template, error) { template.TimeTilDormantAutoDelete = int64(options.TimeTilDormantAutoDelete) From 75d73f88c4132b8a1b14e3f77c140bb983378f4d Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:20:35 +0000 Subject: [PATCH 3/3] refactor(coderd): share dormancy deletion label helper --- coderd/autobuild/lifecycle_executor.go | 10 +--------- coderd/notifications/dormancy.go | 16 ++++++++++++++++ coderd/workspaces.go | 10 +--------- 3 files changed, 18 insertions(+), 18 deletions(-) create mode 100644 coderd/notifications/dormancy.go diff --git a/coderd/autobuild/lifecycle_executor.go b/coderd/autobuild/lifecycle_executor.go index f6f84c9912b3d..9e7e5dd071f4e 100644 --- a/coderd/autobuild/lifecycle_executor.go +++ b/coderd/autobuild/lifecycle_executor.go @@ -11,7 +11,6 @@ import ( "sync/atomic" "time" - "github.com/dustin/go-humanize" "github.com/google/uuid" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -469,13 +468,6 @@ func (e *Executor) runOnce(t time.Time) Stats { } } if shouldNotifyDormancy { - // The body renders this label in its "will be automatically - // deleted in ..." sentence, so it must carry the auto-delete - // countdown, or generic wording when there is no deadline. - timeTilDelete := "line with your template's auto-deletion policy" - if ws.DeletingAt.Valid { - timeTilDelete = humanize.Time(ws.DeletingAt.Time) - } _, err = e.notificationsEnqueuer.Enqueue( e.ctx, ws.OwnerID, @@ -483,7 +475,7 @@ func (e *Executor) runOnce(t time.Time) Stats { map[string]string{ "name": ws.Name, "reason": "inactivity exceeded the dormancy threshold", - "timeTilDormant": timeTilDelete, + "timeTilDormant": notifications.DormantDeletionText(ws.DeletingAt), }, "lifecycle_executor", ws.ID, diff --git a/coderd/notifications/dormancy.go b/coderd/notifications/dormancy.go new file mode 100644 index 0000000000000..236bcd130e996 --- /dev/null +++ b/coderd/notifications/dormancy.go @@ -0,0 +1,16 @@ +package notifications + +import ( + "database/sql" + + "github.com/dustin/go-humanize" +) + +// DormantDeletionText supplies the timeTilDormant label embedded in the stored +// TemplateWorkspaceDormant "will be automatically deleted in ..." sentence. +func DormantDeletionText(deletingAt sql.NullTime) string { + if deletingAt.Valid { + return humanize.Time(deletingAt.Time) + } + return "line with your template's auto-deletion policy" +} diff --git a/coderd/workspaces.go b/coderd/workspaces.go index a302a12ac2356..2bbaaf5e65b5f 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -12,7 +12,6 @@ import ( "strings" "time" - "github.com/dustin/go-humanize" "github.com/go-chi/chi/v5" "github.com/google/uuid" "golang.org/x/sync/errgroup" @@ -1515,13 +1514,6 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) { } if initiatorErr == nil { - // The body renders this label in its "will be automatically - // deleted in ..." sentence, so it must carry the auto-delete - // countdown, or generic wording when there is no deadline. - timeTilDelete := "line with your template's auto-deletion policy" - if newWorkspace.DeletingAt.Valid { - timeTilDelete = humanize.Time(newWorkspace.DeletingAt.Time) - } _, err = api.NotificationsEnqueuer.Enqueue( // nolint:gocritic // Need notifier actor to enqueue notifications dbauthz.AsNotifier(ctx), @@ -1530,7 +1522,7 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) { map[string]string{ "name": newWorkspace.Name, "reason": "a " + initiator.Username + " request", - "timeTilDormant": timeTilDelete, + "timeTilDormant": notifications.DormantDeletionText(newWorkspace.DeletingAt), }, "api", newWorkspace.ID,