From d39f69f4c2040d5fcf027fd7beb2b438475e82f9 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 11 Mar 2026 09:29:10 -0700 Subject: [PATCH] fix: avoid mutating proto App.Healthcheck in insertAgentApp (#22954) ## Problem `insertAgentApp` mutated its input by writing to `app.Healthcheck` when it was nil (line 3525): ```go if app.Healthcheck == nil { app.Healthcheck = &sdkproto.Healthcheck{} // mutation! } ``` The Devcontainers subtests share the same `tt.resource` pointer across two parallel goroutines (`WithProtoIDs` and `WithoutProtoIDs`), causing a data race on the `Healthcheck` field (and its sub-fields `Url`, `Interval`, `Threshold`). ## Fix Replace the in-place mutation with a local variable: ```go healthcheck := app.GetHealthcheck() if healthcheck == nil { healthcheck = &sdkproto.Healthcheck{} } ``` This avoids writing back to the shared proto message. All downstream reads now use the local `healthcheck` variable. --- coderd/provisionerdserver/provisionerdserver.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 16109fab9a..5bf54a05c6 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -3521,10 +3521,11 @@ func insertAgentApp(ctx context.Context, db database.Store, agentID uuid.UUID, a appSlugs[slug] = struct{}{} health := database.WorkspaceAppHealthDisabled - if app.Healthcheck == nil { - app.Healthcheck = &sdkproto.Healthcheck{} + healthcheck := app.GetHealthcheck() + if healthcheck == nil { + healthcheck = &sdkproto.Healthcheck{} } - if app.Healthcheck.Url != "" { + if healthcheck.Url != "" { health = database.WorkspaceAppHealthInitializing } @@ -3579,9 +3580,9 @@ func insertAgentApp(ctx context.Context, db database.Store, agentID uuid.UUID, a External: app.External, Subdomain: app.Subdomain, SharingLevel: sharingLevel, - HealthcheckUrl: app.Healthcheck.Url, - HealthcheckInterval: app.Healthcheck.Interval, - HealthcheckThreshold: app.Healthcheck.Threshold, + HealthcheckUrl: healthcheck.Url, + HealthcheckInterval: healthcheck.Interval, + HealthcheckThreshold: healthcheck.Threshold, Health: health, // #nosec G115 - Order represents a display order value that's always small and fits in int32 DisplayOrder: int32(app.Order),