fix: prevent activity bump for prebuilt workspaces (#19263)

## Description

This PR ensures that activity-based deadline extensions ("activity
bumping") are not applied to prebuilt workspaces. Prebuilds are managed
by the reconciliation loop and must not have `deadline` or
`max_deadline` values set or extended, as they are not part of the
regular lifecycle executor path.

## Changes

- Update `ActivityBumpWorkspace` SQL query to discard prebuilt
workspaces
- Update application layer to avoid calling activity bump logic on
prebuilt workspaces

Related with: 
* Issue: https://github.com/coder/coder/issues/18898
* PR: https://github.com/coder/coder/pull/19252
This commit is contained in:
Susana Ferreira
2025-08-20 12:19:14 +01:00
committed by GitHub
parent cd1faffeff
commit 560cf84251
4 changed files with 148 additions and 28 deletions
+6 -2
View File
@@ -32,7 +32,7 @@ WITH latest AS (
-- be as if the workspace auto started at the given time and the
-- original TTL was applied.
--
-- Sadly we can't define ` + "`" + `activity_bump_interval` + "`" + ` above since
-- Sadly we can't define 'activity_bump_interval' above since
-- it won't be available for this CASE statement, so we have to
-- copy the cast twice.
WHEN NOW() + (templates.activity_bump / 1000 / 1000 / 1000 || ' seconds')::interval > $1 :: timestamptz
@@ -62,7 +62,11 @@ WITH latest AS (
ON workspaces.id = workspace_builds.workspace_id
JOIN templates
ON templates.id = workspaces.template_id
WHERE workspace_builds.workspace_id = $2::uuid
WHERE
workspace_builds.workspace_id = $2::uuid
-- Prebuilt workspaces (identified by having the prebuilds system user as owner_id)
-- are managed by the reconciliation loop and not subject to activity bumping
AND workspaces.owner_id != 'c42fdf75-3097-471c-8c33-fb52454d81c0'::UUID
ORDER BY workspace_builds.build_number DESC
LIMIT 1
)
+6 -2
View File
@@ -22,7 +22,7 @@ WITH latest AS (
-- be as if the workspace auto started at the given time and the
-- original TTL was applied.
--
-- Sadly we can't define `activity_bump_interval` above since
-- Sadly we can't define 'activity_bump_interval' above since
-- it won't be available for this CASE statement, so we have to
-- copy the cast twice.
WHEN NOW() + (templates.activity_bump / 1000 / 1000 / 1000 || ' seconds')::interval > @next_autostart :: timestamptz
@@ -52,7 +52,11 @@ WITH latest AS (
ON workspaces.id = workspace_builds.workspace_id
JOIN templates
ON templates.id = workspaces.template_id
WHERE workspace_builds.workspace_id = @workspace_id::uuid
WHERE
workspace_builds.workspace_id = @workspace_id::uuid
-- Prebuilt workspaces (identified by having the prebuilds system user as owner_id)
-- are managed by the reconciliation loop and not subject to activity bumping
AND workspaces.owner_id != 'c42fdf75-3097-471c-8c33-fb52454d81c0'::UUID
ORDER BY workspace_builds.build_number DESC
LIMIT 1
)
+27 -24
View File
@@ -149,33 +149,36 @@ func (r *Reporter) ReportAgentStats(ctx context.Context, now time.Time, workspac
return nil
}
// check next autostart
var nextAutostart time.Time
if workspace.AutostartSchedule.String != "" {
templateSchedule, err := (*(r.opts.TemplateScheduleStore.Load())).Get(ctx, r.opts.Database, workspace.TemplateID)
// If the template schedule fails to load, just default to bumping
// without the next transition and log it.
switch {
case err == nil:
next, allowed := schedule.NextAutostart(now, workspace.AutostartSchedule.String, templateSchedule)
if allowed {
nextAutostart = next
// Prebuilds are not subject to activity-based deadline bumps
if !workspace.IsPrebuild() {
// check next autostart
var nextAutostart time.Time
if workspace.AutostartSchedule.String != "" {
templateSchedule, err := (*(r.opts.TemplateScheduleStore.Load())).Get(ctx, r.opts.Database, workspace.TemplateID)
// If the template schedule fails to load, just default to bumping
// without the next transition and log it.
switch {
case err == nil:
next, allowed := schedule.NextAutostart(now, workspace.AutostartSchedule.String, templateSchedule)
if allowed {
nextAutostart = next
}
case database.IsQueryCanceledError(err):
r.opts.Logger.Debug(ctx, "query canceled while loading template schedule",
slog.F("workspace_id", workspace.ID),
slog.F("template_id", workspace.TemplateID))
default:
r.opts.Logger.Error(ctx, "failed to load template schedule bumping activity, defaulting to bumping by 60min",
slog.F("workspace_id", workspace.ID),
slog.F("template_id", workspace.TemplateID),
slog.Error(err),
)
}
case database.IsQueryCanceledError(err):
r.opts.Logger.Debug(ctx, "query canceled while loading template schedule",
slog.F("workspace_id", workspace.ID),
slog.F("template_id", workspace.TemplateID))
default:
r.opts.Logger.Error(ctx, "failed to load template schedule bumping activity, defaulting to bumping by 60min",
slog.F("workspace_id", workspace.ID),
slog.F("template_id", workspace.TemplateID),
slog.Error(err),
)
}
}
// bump workspace activity
ActivityBumpWorkspace(ctx, r.opts.Logger.Named("activity_bump"), r.opts.Database, workspace.ID, nextAutostart)
// bump workspace activity
ActivityBumpWorkspace(ctx, r.opts.Logger.Named("activity_bump"), r.opts.Database, workspace.ID, nextAutostart)
}
// bump workspace last_used_at
r.opts.UsageTracker.Add(workspace.ID)