@@ -257,28 +257,7 @@ func (c *StoreReconciler) ReconcileAll(ctx context.Context) error {
257257return xerrors .Errorf ("determine current snapshot: %w" ,err )
258258}
259259
260- presetsMap := make (map [hardLimitedPresetKey ][]database.GetTemplatePresetsWithPrebuildsRow )
261- for _ ,preset := range snapshot .Presets {
262- key := hardLimitedPresetKey {
263- orgName :preset .OrganizationName ,
264- templateName :preset .TemplateName ,
265- presetName :preset .Name ,
266- }
267-
268- presetsMap [key ]= append (presetsMap [key ],preset )
269- }
270-
271- isPresetHardLimited := make (map [hardLimitedPresetKey ]bool )
272- for key ,presets := range presetsMap {
273- for _ ,preset := range presets {
274- if preset .UsingActiveVersion && ! preset .Deleted && snapshot .IsHardLimited (preset .ID ) {
275- isPresetHardLimited [key ]= true
276- break
277- }
278- }
279- }
280-
281- c .metrics .trackHardLimitedStatus (isPresetHardLimited )
260+ c .reportHardLimitedPresets (snapshot )
282261
283262if len (snapshot .Presets )== 0 {
284263logger .Debug (ctx ,"no templates found with prebuilds configured" )
@@ -320,6 +299,56 @@ func (c *StoreReconciler) ReconcileAll(ctx context.Context) error {
320299return err
321300}
322301
302+ // Report a metric only if the preset uses the latest version of the template and the template is not deleted.
303+ // This avoids conflicts between metrics from old and new template versions.
304+ //
305+ // NOTE: Multiple versions of a preset can exist with the same orgName, templateName, and presetName,
306+ // because templates can have multiple versions — or deleted templates can share the same name.
307+ //
308+ // The safest approach is to report the metric only for the latest version of the preset.
309+ // When a new template version is released, the metric for the new preset should overwrite
310+ // the old value in Prometheus.
311+ //
312+ // However, there’s one edge case: if an admin creates a template, it becomes hard-limited,
313+ // then deletes the template and never creates another with the same name,
314+ // the old preset will continue to be reported as hard-limited —
315+ // even though it’s deleted. This will persist until `coderd` is restarted.
316+
317+ func (c * StoreReconciler )reportHardLimitedPresets (snapshot * prebuilds.GlobalSnapshot ) {
318+ // presetsMap is a map from key (orgName:templateName:presetName) to list of corresponding presets.
319+ // Multiple versions of a preset can exist with the same orgName, templateName, and presetName,
320+ // because templates can have multiple versions — or deleted templates can share the same name.
321+ presetsMap := make (map [hardLimitedPresetKey ][]database.GetTemplatePresetsWithPrebuildsRow )
322+ for _ ,preset := range snapshot .Presets {
323+ key := hardLimitedPresetKey {
324+ orgName :preset .OrganizationName ,
325+ templateName :preset .TemplateName ,
326+ presetName :preset .Name ,
327+ }
328+
329+ presetsMap [key ]= append (presetsMap [key ],preset )
330+ }
331+
332+ // Report a preset as hard-limited only if all the following conditions are met:
333+ // - The preset is marked as hard-limited
334+ // - The preset is using the active version of its template, and the template has not been deleted
335+ //
336+ // The second condition is important because a hard-limited preset that has become outdated is no longer relevant.
337+ // Its associated prebuilt workspaces were likely deleted, and it's not meaningful to continue reporting it
338+ // as hard-limited to the admin.
339+ isPresetHardLimited := make (map [hardLimitedPresetKey ]bool )
340+ for key ,presets := range presetsMap {
341+ for _ ,preset := range presets {
342+ if preset .UsingActiveVersion && ! preset .Deleted && snapshot .IsHardLimited (preset .ID ) {
343+ isPresetHardLimited [key ]= true
344+ break
345+ }
346+ }
347+ }
348+
349+ c .metrics .registerHardLimitedPresets (isPresetHardLimited )
350+ }
351+
323352// SnapshotState captures the current state of all prebuilds across templates.
324353func (c * StoreReconciler )SnapshotState (ctx context.Context ,store database.Store ) (* prebuilds.GlobalSnapshot ,error ) {
325354if err := ctx .Err ();err != nil {