@@ -29,6 +29,7 @@ import (
2929"github.com/coder/coder/v2/coderd/database/provisionerjobs"
3030"github.com/coder/coder/v2/coderd/database/pubsub"
3131"github.com/coder/coder/v2/coderd/notifications"
32+ "github.com/coder/coder/v2/coderd/provisionerdserver"
3233"github.com/coder/coder/v2/coderd/schedule"
3334"github.com/coder/coder/v2/coderd/wsbuilder"
3435"github.com/coder/coder/v2/codersdk"
@@ -132,6 +133,39 @@ func (e *Executor) Run() {
132133})
133134}
134135
136+ // hasValidProvisioner checks whether there is at least one valid (non-stale, correct tags) provisioner
137+ // based on time t and the tags maps (such as from a templateVersionJob).
138+ func (e * Executor )hasValidProvisioner (ctx context.Context ,tx database.Store ,t time.Time ,ws database.Workspace ,tags map [string ]string ) (bool ,error ) {
139+ queryParams := database.GetProvisionerDaemonsByOrganizationParams {
140+ OrganizationID :ws .OrganizationID ,
141+ WantTags :tags ,
142+ }
143+
144+ // nolint: gocritic // The user (in this case, the user/context for autostart builds) may not have the full
145+ // permissions to read provisioner daemons, but we need to check if there's any for the job prior to the
146+ // execution of the job via autostart to fix: https://github.com/coder/coder/issues/17941
147+ provisionerDaemons ,err := tx .GetProvisionerDaemonsByOrganization (dbauthz .AsSystemReadProvisionerDaemons (ctx ),queryParams )
148+ if err != nil {
149+ return false ,xerrors .Errorf ("get provisioner daemons: %w" ,err )
150+ }
151+
152+ logger := e .log .With (slog .F ("tags" ,tags ))
153+ // Check if any provisioners are active (not stale)
154+ for _ ,pd := range provisionerDaemons {
155+ if pd .LastSeenAt .Valid {
156+ age := t .Sub (pd .LastSeenAt .Time )
157+ if age <= provisionerdserver .StaleInterval {
158+ logger .Debug (ctx ,"hasValidProvisioner: found active provisioner" ,
159+ slog .F ("daemon_id" ,pd .ID ),
160+ )
161+ return true ,nil
162+ }
163+ }
164+ }
165+ logger .Debug (ctx ,"hasValidProvisioner: no active provisioners found" )
166+ return false ,nil
167+ }
168+
135169func (e * Executor )runOnce (t time.Time )Stats {
136170stats := Stats {
137171Transitions :make (map [uuid.UUID ]database.WorkspaceTransition ),
@@ -281,6 +315,22 @@ func (e *Executor) runOnce(t time.Time) Stats {
281315return nil
282316}
283317
318+ // Get the template version job to access tags
319+ templateVersionJob ,err := tx .GetProvisionerJobByID (e .ctx ,activeTemplateVersion .JobID )
320+ if err != nil {
321+ return xerrors .Errorf ("get template version job: %w" ,err )
322+ }
323+
324+ // Before creating the workspace build, check for available provisioners
325+ hasProvisioners ,err := e .hasValidProvisioner (e .ctx ,tx ,t ,ws ,templateVersionJob .Tags )
326+ if err != nil {
327+ return xerrors .Errorf ("check provisioner availability: %w" ,err )
328+ }
329+ if ! hasProvisioners {
330+ log .Warn (e .ctx ,"skipping autostart - no available provisioners" )
331+ return nil // Skip this workspace
332+ }
333+
284334if nextTransition != "" {
285335builder := wsbuilder .New (ws ,nextTransition ,* e .buildUsageChecker .Load ()).
286336SetLastWorkspaceBuildInTx (& latestBuild ).