From 872aef3af963a95d7494238fef9083335f8debed Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 26 Jun 2025 15:49:58 +0300 Subject: [PATCH 1/9] feat(.devcontainer): install dotfiles if present (#18606) --- .devcontainer/devcontainer.json | 17 +++++++--- .devcontainer/postCreateCommand.sh | 54 ++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) create mode 100755 .devcontainer/postCreateCommand.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a9339f7e0cf7e..8d16afcda362a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,6 @@ { "name": "Development environments on your infrastructure", "image": "codercom/oss-dogfood:latest", - "features": { // See all possible options here https://github.com/devcontainers/features/tree/main/src/docker-in-docker "ghcr.io/devcontainers/features/docker-in-docker:2": { @@ -13,10 +12,20 @@ } }, // SYS_PTRACE to enable go debugging - "runArgs": ["--cap-add=SYS_PTRACE"], + "runArgs": [ + "--cap-add=SYS_PTRACE" + ], "customizations": { "vscode": { - "extensions": ["biomejs.biome"] + "extensions": [ + "biomejs.biome" + ] } - } + }, + "mounts": [ + // Mount the entire home because conditional mounts are not supported. + // See: https://github.com/devcontainers/spec/issues/132 + "source=${localEnv:HOME},target=/mnt/home/coder,type=bind,readonly" + ], + "postCreateCommand": "./.devcontainer/postCreateCommand.sh" } diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh new file mode 100755 index 0000000000000..bad584e85bea9 --- /dev/null +++ b/.devcontainer/postCreateCommand.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +install_ssh_config() { + echo "🔑 Installing SSH configuration..." + rsync -a /mnt/home/coder/.ssh/ ~/.ssh/ + chmod 0700 ~/.ssh +} + +install_git_config() { + echo "📂 Installing Git configuration..." + if [ -f /mnt/home/coder/git/config ]; then + rsync -a /mnt/home/coder/git/ ~/.config/git/ + elif [ -d /mnt/home/coder/.gitconfig ]; then + rsync -a /mnt/home/coder/.gitconfig ~/.gitconfig + else + echo "⚠️ Git configuration directory not found." + fi +} + +install_dotfiles() { + if [ ! -d /mnt/home/coder/.config/coderv2/dotfiles ]; then + echo "⚠️ Dotfiles directory not found." + return + fi + + cd /mnt/home/coder/.config/coderv2/dotfiles || return + for script in install.sh install bootstrap.sh bootstrap script/bootstrap setup.sh setup script/setup; do + if [ -x $script ]; then + echo "📦 Installing dotfiles..." + ./$script || { + echo "❌ Error running $script. Please check the script for issues." + return + } + echo "✅ Dotfiles installed successfully." + return + fi + done + echo "⚠️ No install script found in dotfiles directory." +} + +personalize() { + # Allow script to continue as Coder dogfood utilizes a hack to + # synchronize startup script execution. + touch /tmp/.coder-startup-script.done + + if [ -x /mnt/home/coder/personalize ]; then + echo "🎨 Personalizing environment..." + /mnt/home/coder/personalize + fi +} + +install_ssh_config +install_dotfiles +personalize From c6e0ba12d3face3d5112bacc17452ffccb235d0d Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Thu, 26 Jun 2025 15:54:52 +0200 Subject: [PATCH 2/9] feat: graduate prebuilds to general availability (#18607) This PR removes the prebuilds experiment and allows the use of prebuilds without opting into an experiment. --- cli/testdata/coder_server_--help.golden | 6 +++ coderd/apidoc/docs.go | 7 +--- coderd/apidoc/swagger.json | 7 +--- coderd/database/dbauthz/dbauthz_test.go | 3 +- coderd/database/dbmem/dbmem.go | 2 +- coderd/telemetry/telemetry.go | 4 -- coderd/telemetry/telemetry_test.go | 19 +-------- codersdk/deployment.go | 7 +--- .../prebuilt-workspaces.md | 1 - docs/reference/api/schemas.md | 1 - docs/reference/cli/server.md | 11 ++++++ .../cli/testdata/coder_server_--help.golden | 6 +++ enterprise/coderd/coderd.go | 13 ++----- enterprise/coderd/coderd_test.go | 39 +++++-------------- enterprise/coderd/workspaceagents_test.go | 1 - enterprise/coderd/workspaces_test.go | 5 +-- site/src/api/typesGenerated.ts | 2 - 17 files changed, 46 insertions(+), 88 deletions(-) diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden index 19857cf8ebe76..4b1fa1ca4e6c9 100644 --- a/cli/testdata/coder_server_--help.golden +++ b/cli/testdata/coder_server_--help.golden @@ -677,6 +677,12 @@ workspaces stopping during the day due to template scheduling. must be *. Only one hour and minute can be specified (ranges or comma separated values are not supported). +WORKSPACE PREBUILDS OPTIONS: +Configure how workspace prebuilds behave. + + --workspace-prebuilds-reconciliation-interval duration, $CODER_WORKSPACE_PREBUILDS_RECONCILIATION_INTERVAL (default: 15s) + How often to reconcile workspace prebuilds state. + ⚠️ DANGEROUS OPTIONS: --dangerous-allow-path-app-sharing bool, $CODER_DANGEROUS_ALLOW_PATH_APP_SHARING Allow workspace apps that are not served from subdomains to be shared. diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index cbcfe1d72b380..522ba671a9a63 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -12194,15 +12194,13 @@ const docTemplate = `{ "auto-fill-parameters", "notifications", "workspace-usage", - "web-push", - "workspace-prebuilds" + "web-push" ], "x-enum-comments": { "ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.", "ExperimentExample": "This isn't used for anything.", "ExperimentNotifications": "Sends notifications via SMTP and webhooks following certain events.", "ExperimentWebPush": "Enables web push notifications through the browser.", - "ExperimentWorkspacePrebuilds": "Enables the new workspace prebuilds feature.", "ExperimentWorkspaceUsage": "Enables the new workspace usage tracking." }, "x-enum-varnames": [ @@ -12210,8 +12208,7 @@ const docTemplate = `{ "ExperimentAutoFillParameters", "ExperimentNotifications", "ExperimentWorkspaceUsage", - "ExperimentWebPush", - "ExperimentWorkspacePrebuilds" + "ExperimentWebPush" ] }, "codersdk.ExternalAuth": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index ef723652a0941..abcae550a4ec5 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -10927,15 +10927,13 @@ "auto-fill-parameters", "notifications", "workspace-usage", - "web-push", - "workspace-prebuilds" + "web-push" ], "x-enum-comments": { "ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.", "ExperimentExample": "This isn't used for anything.", "ExperimentNotifications": "Sends notifications via SMTP and webhooks following certain events.", "ExperimentWebPush": "Enables web push notifications through the browser.", - "ExperimentWorkspacePrebuilds": "Enables the new workspace prebuilds feature.", "ExperimentWorkspaceUsage": "Enables the new workspace usage tracking." }, "x-enum-varnames": [ @@ -10943,8 +10941,7 @@ "ExperimentAutoFillParameters", "ExperimentNotifications", "ExperimentWorkspaceUsage", - "ExperimentWebPush", - "ExperimentWorkspacePrebuilds" + "ExperimentWebPush" ] }, "codersdk.ExternalAuth": { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 0361bf796cb55..6d1c8c3df601c 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -5059,8 +5059,7 @@ func (s *MethodTestSuite) TestPrebuilds() { })) s.Run("GetPrebuildMetrics", s.Subtest(func(_ database.Store, check *expects) { check.Args(). - Asserts(rbac.ResourceWorkspace.All(), policy.ActionRead). - ErrorsWithInMemDB(dbmem.ErrUnimplemented) + Asserts(rbac.ResourceWorkspace.All(), policy.ActionRead) })) s.Run("CountInProgressPrebuilds", s.Subtest(func(_ database.Store, check *expects) { check.Args(). diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index e5604d440073b..cd1067e61dbb5 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -4270,7 +4270,7 @@ func (q *FakeQuerier) GetParameterSchemasByJobID(_ context.Context, jobID uuid.U } func (*FakeQuerier) GetPrebuildMetrics(_ context.Context) ([]database.GetPrebuildMetricsRow, error) { - return nil, ErrUnimplemented + return make([]database.GetPrebuildMetricsRow, 0), nil } func (q *FakeQuerier) GetPresetByID(ctx context.Context, presetID uuid.UUID) (database.GetPresetByIDRow, error) { diff --git a/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index dfc27418f4862..747cf2cb47de1 100644 --- a/coderd/telemetry/telemetry.go +++ b/coderd/telemetry/telemetry.go @@ -687,10 +687,6 @@ func (r *remoteReporter) createSnapshot() (*Snapshot, error) { return nil }) eg.Go(func() error { - if !r.options.Experiments.Enabled(codersdk.ExperimentWorkspacePrebuilds) { - return nil - } - metrics, err := r.options.Database.GetPrebuildMetrics(ctx) if err != nil { return xerrors.Errorf("get prebuild metrics: %w", err) diff --git a/coderd/telemetry/telemetry_test.go b/coderd/telemetry/telemetry_test.go index 9338e87d6d31c..ac836317b680e 100644 --- a/coderd/telemetry/telemetry_test.go +++ b/coderd/telemetry/telemetry_test.go @@ -408,7 +408,6 @@ func TestPrebuiltWorkspacesTelemetry(t *testing.T) { cases := []struct { name string - experimentEnabled bool storeFn func(store database.Store) database.Store expectedSnapshotEntries int expectedCreated int @@ -416,8 +415,7 @@ func TestPrebuiltWorkspacesTelemetry(t *testing.T) { expectedClaimed int }{ { - name: "experiment enabled", - experimentEnabled: true, + name: "prebuilds enabled", storeFn: func(store database.Store) database.Store { return &mockDB{Store: store} }, @@ -427,19 +425,11 @@ func TestPrebuiltWorkspacesTelemetry(t *testing.T) { expectedClaimed: 3, }, { - name: "experiment enabled, prebuilds not used", - experimentEnabled: true, + name: "prebuilds not used", storeFn: func(store database.Store) database.Store { return &emptyMockDB{Store: store} }, }, - { - name: "experiment disabled", - experimentEnabled: false, - storeFn: func(store database.Store) database.Store { - return &mockDB{Store: store} - }, - }, } for _, tc := range cases { @@ -448,11 +438,6 @@ func TestPrebuiltWorkspacesTelemetry(t *testing.T) { deployment, snapshot := collectSnapshot(ctx, t, db, func(opts telemetry.Options) telemetry.Options { opts.Database = tc.storeFn(db) - if tc.experimentEnabled { - opts.Experiments = codersdk.Experiments{ - codersdk.ExperimentWorkspacePrebuilds, - } - } return opts }) diff --git a/codersdk/deployment.go b/codersdk/deployment.go index ef0b4eaa07821..544e98f6f2a72 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -3070,7 +3070,6 @@ Write out the current server config as YAML to stdout.`, Group: &deploymentGroupPrebuilds, YAML: "reconciliation_interval", Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"), - Hidden: ExperimentsSafe.Enabled(ExperimentWorkspacePrebuilds), // Hide setting while this feature is experimental. }, { Name: "Reconciliation Backoff Interval", @@ -3342,7 +3341,6 @@ const ( ExperimentNotifications Experiment = "notifications" // Sends notifications via SMTP and webhooks following certain events. ExperimentWorkspaceUsage Experiment = "workspace-usage" // Enables the new workspace usage tracking. ExperimentWebPush Experiment = "web-push" // Enables web push notifications through the browser. - ExperimentWorkspacePrebuilds Experiment = "workspace-prebuilds" // Enables the new workspace prebuilds feature. ) // ExperimentsKnown should include all experiments defined above. @@ -3352,16 +3350,13 @@ var ExperimentsKnown = Experiments{ ExperimentNotifications, ExperimentWorkspaceUsage, ExperimentWebPush, - ExperimentWorkspacePrebuilds, } // ExperimentsSafe should include all experiments that are safe for // users to opt-in to via --experimental='*'. // Experiments that are not ready for consumption by all users should // not be included here and will be essentially hidden. -var ExperimentsSafe = Experiments{ - ExperimentWorkspacePrebuilds, -} +var ExperimentsSafe = Experiments{} // Experiments is a list of experiments. // Multiple experiments may be enabled at the same time. diff --git a/docs/admin/templates/extending-templates/prebuilt-workspaces.md b/docs/admin/templates/extending-templates/prebuilt-workspaces.md index 08a404e040159..9d33425019b50 100644 --- a/docs/admin/templates/extending-templates/prebuilt-workspaces.md +++ b/docs/admin/templates/extending-templates/prebuilt-workspaces.md @@ -27,7 +27,6 @@ Prebuilt workspaces are tightly integrated with [workspace presets](./parameters - [**Premium license**](../../licensing/index.md) - **Compatible Terraform provider**: Use `coder/coder` Terraform provider `>= 2.4.1`. -- **Feature flag**: Enable the `workspace-prebuilds` [experiment](../../../reference/cli/server.md#--experiments). ## Enable prebuilt workspaces for template presets diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index e19c9d15da413..79c6f817bc776 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -2996,7 +2996,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `notifications` | | `workspace-usage` | | `web-push` | -| `workspace-prebuilds` | ## codersdk.ExternalAuth diff --git a/docs/reference/cli/server.md b/docs/reference/cli/server.md index 644065d35076f..f52b3666a866e 100644 --- a/docs/reference/cli/server.md +++ b/docs/reference/cli/server.md @@ -1615,6 +1615,17 @@ Enable Coder Inbox. The upper limit of attempts to send a notification. +### --workspace-prebuilds-reconciliation-interval + +| | | +|-------------|-----------------------------------------------------------------| +| Type | duration | +| Environment | $CODER_WORKSPACE_PREBUILDS_RECONCILIATION_INTERVAL | +| YAML | workspace_prebuilds.reconciliation_interval | +| Default | 15s | + +How often to reconcile workspace prebuilds state. + ### --hide-ai-tasks | | | diff --git a/enterprise/cli/testdata/coder_server_--help.golden b/enterprise/cli/testdata/coder_server_--help.golden index 3e3868c5ae432..d7c26bc537693 100644 --- a/enterprise/cli/testdata/coder_server_--help.golden +++ b/enterprise/cli/testdata/coder_server_--help.golden @@ -678,6 +678,12 @@ workspaces stopping during the day due to template scheduling. must be *. Only one hour and minute can be specified (ranges or comma separated values are not supported). +WORKSPACE PREBUILDS OPTIONS: +Configure how workspace prebuilds behave. + + --workspace-prebuilds-reconciliation-interval duration, $CODER_WORKSPACE_PREBUILDS_RECONCILIATION_INTERVAL (default: 15s) + How often to reconcile workspace prebuilds state. + ⚠️ DANGEROUS OPTIONS: --dangerous-allow-path-app-sharing bool, $CODER_DANGEROUS_ALLOW_PATH_APP_SHARING Allow workspace apps that are not served from subdomains to be shared. diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index 9b168c8e3f366..601700403f326 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -1150,16 +1150,9 @@ func (api *API) Authorize(r *http.Request, action policy.Action, object rbac.Obj // nolint:revive // featureEnabled is a legit control flag. func (api *API) setupPrebuilds(featureEnabled bool) (agplprebuilds.ReconciliationOrchestrator, agplprebuilds.Claimer) { - experimentEnabled := api.AGPL.Experiments.Enabled(codersdk.ExperimentWorkspacePrebuilds) - if !experimentEnabled || !featureEnabled { - levelFn := api.Logger.Debug - // If the experiment is enabled but the license does not entitle the feature, operators should be warned. - if !featureEnabled { - levelFn = api.Logger.Warn - } - - levelFn(context.Background(), "prebuilds not enabled; ensure you have a premium license and the 'workspace-prebuilds' experiment set", - slog.F("experiment_enabled", experimentEnabled), slog.F("feature_enabled", featureEnabled)) + if !featureEnabled { + api.Logger.Warn(context.Background(), "prebuilds not enabled; ensure you have a premium license", + slog.F("feature_enabled", featureEnabled)) return agplprebuilds.DefaultReconciler, agplprebuilds.DefaultClaimer } diff --git a/enterprise/coderd/coderd_test.go b/enterprise/coderd/coderd_test.go index 1c8c863db040b..89a61c657e21a 100644 --- a/enterprise/coderd/coderd_test.go +++ b/enterprise/coderd/coderd_test.go @@ -260,34 +260,19 @@ func TestEntitlements_Prebuilds(t *testing.T) { t.Parallel() cases := []struct { - name string - experimentEnabled bool - featureEnabled bool - expectedEnabled bool + name string + featureEnabled bool + expectedEnabled bool }{ { - name: "Fully enabled", - featureEnabled: true, - experimentEnabled: true, - expectedEnabled: true, + name: "Feature enabled", + featureEnabled: true, + expectedEnabled: true, }, { - name: "Feature disabled", - featureEnabled: false, - experimentEnabled: true, - expectedEnabled: false, - }, - { - name: "Experiment disabled", - featureEnabled: true, - experimentEnabled: false, - expectedEnabled: false, - }, - { - name: "Fully disabled", - featureEnabled: false, - experimentEnabled: false, - expectedEnabled: false, + name: "Feature disabled", + featureEnabled: false, + expectedEnabled: false, }, } @@ -302,11 +287,7 @@ func TestEntitlements_Prebuilds(t *testing.T) { _, _, api, _ := coderdenttest.NewWithAPI(t, &coderdenttest.Options{ Options: &coderdtest.Options{ - DeploymentValues: coderdtest.DeploymentValues(t, func(values *codersdk.DeploymentValues) { - if tc.experimentEnabled { - values.Experiments = serpent.StringArray{string(codersdk.ExperimentWorkspacePrebuilds)} - } - }), + DeploymentValues: coderdtest.DeploymentValues(t), }, EntitlementsUpdateInterval: time.Second, diff --git a/enterprise/coderd/workspaceagents_test.go b/enterprise/coderd/workspaceagents_test.go index 1eea9ecda9ca8..f4f0670cd150e 100644 --- a/enterprise/coderd/workspaceagents_test.go +++ b/enterprise/coderd/workspaceagents_test.go @@ -112,7 +112,6 @@ func TestReinitializeAgent(t *testing.T) { Pubsub: ps, DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) { dv.Prebuilds.ReconciliationInterval = serpent.Duration(time.Second) - dv.Experiments.Append(string(codersdk.ExperimentWorkspacePrebuilds)) }), }, LicenseOptions: &coderdenttest.LicenseOptions{ diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 228b11f485a96..3bed052702637 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -531,10 +531,7 @@ func TestCreateUserWorkspace(t *testing.T) { client, db, user := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{ Options: &coderdtest.Options{ - DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) { - err := dv.Experiments.Append(string(codersdk.ExperimentWorkspacePrebuilds)) - require.NoError(t, err) - }), + DeploymentValues: coderdtest.DeploymentValues(t), }, LicenseOptions: &coderdenttest.LicenseOptions{ Features: license.Features{ diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index b2117cf15c987..0e6a481406d8b 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -795,7 +795,6 @@ export type Experiment = | "example" | "notifications" | "web-push" - | "workspace-prebuilds" | "workspace-usage"; export const Experiments: Experiment[] = [ @@ -803,7 +802,6 @@ export const Experiments: Experiment[] = [ "example", "notifications", "web-push", - "workspace-prebuilds", "workspace-usage", ]; From 87d052ea93a370c0e4b2ee604bc778f6c737a06b Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 26 Jun 2025 17:09:31 +0300 Subject: [PATCH 3/9] feat(.devcontainer): add cursor, filebrowser, windsurf and zed (#18608) --- .devcontainer/devcontainer.json | 31 +++++++++- .../filebrowser/devcontainer-feature.json | 50 +++++++++++++++++ .devcontainer/filebrowser/install.sh | 56 +++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/filebrowser/devcontainer-feature.json create mode 100644 .devcontainer/filebrowser/install.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8d16afcda362a..826d3fd38e6c2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -9,7 +9,8 @@ "ghcr.io/coder/devcontainer-features/code-server:1": { "auth": "none", "port": 13337 - } + }, + "./filebrowser": {} }, // SYS_PTRACE to enable go debugging "runArgs": [ @@ -20,6 +21,34 @@ "extensions": [ "biomejs.biome" ] + }, + "coder": { + "apps": [ + { + "slug": "cursor", + "displayName": "Cursor Desktop", + "url": "cursor://coder.coder-remote/openDevContainer?owner=${localEnv:CODER_WORKSPACE_OWNER_NAME}&workspace=${localEnv:CODER_WORKSPACE_NAME}&agent=${localEnv:CODER_WORKSPACE_PARENT_AGENT_NAME}&url=${localEnv:CODER_URL}&token=$SESSION_TOKEN&devContainerName=${localEnv:CONTAINER_ID}&devContainerFolder=${containerWorkspaceFolder}", + "external": true, + "icon": "/icon/cursor.svg", + "order": 1 + }, + { + "slug": "windsurf", + "displayName": "Windsurf Editor", + "url": "windsurf://coder.coder-remote/openDevContainer?owner=${localEnv:CODER_WORKSPACE_OWNER_NAME}&workspace=${localEnv:CODER_WORKSPACE_NAME}&agent=${localEnv:CODER_WORKSPACE_PARENT_AGENT_NAME}&url=${localEnv:CODER_URL}&token=$SESSION_TOKEN&devContainerName=${localEnv:CONTAINER_ID}&devContainerFolder=${containerWorkspaceFolder}", + "external": true, + "icon": "/icon/windsurf.svg", + "order": 4 + }, + { + "slug": "zed", + "displayName": "Zed Editor", + "url": "zed://ssh/${localEnv:CODER_WORKSPACE_AGENT_NAME}.${localEnv:CODER_WORKSPACE_NAME}.${localEnv:CODER_WORKSPACE_OWNER_NAME}.coder/${containerWorkspaceFolder}", + "external": true, + "icon": "/icon/zed.svg", + "order": 5 + } + ] } }, "mounts": [ diff --git a/.devcontainer/filebrowser/devcontainer-feature.json b/.devcontainer/filebrowser/devcontainer-feature.json new file mode 100644 index 0000000000000..3829139cf3143 --- /dev/null +++ b/.devcontainer/filebrowser/devcontainer-feature.json @@ -0,0 +1,50 @@ +{ + "id": "filebrowser", + "version": "0.0.1", + "name": "File Browser", + "description": "A web-based file browser for your development container", + "options": { + "port": { + "type": "string", + "default": "13339", + "description": "The port to run filebrowser on" + }, + // "folder": { + // "type": "string", + // "default": "${containerWorkspaceFolder}", + // "description": "The root directory for filebrowser to serve" + // }, + "auth": { + "type": "string", + "enum": [ + "none", + "password" + ], + "default": "none", + "description": "Authentication method (none or password)" + } + }, + "entrypoint": "/usr/local/bin/filebrowser-entrypoint", + "dependsOn": { + "ghcr.io/devcontainers/features/common-utils:2": {} + }, + "customizations": { + "coder": { + "apps": [ + { + "slug": "filebrowser", + "displayName": "File Browser", + "url": "http://localhost:${localEnv:FEATURE_FILEBROWSER_OPTION_PORT:13339}", + "icon": "/icon/filebrowser.svg", + "order": 3, + "subdomain": true, + "healthcheck": { + "url": "http://localhost:${localEnv:FEATURE_FILEBROWSER_OPTION_PORT:13339}/health", + "interval": 5, + "threshold": 6 + } + } + ] + } + } +} diff --git a/.devcontainer/filebrowser/install.sh b/.devcontainer/filebrowser/install.sh new file mode 100644 index 0000000000000..1f8390f63864c --- /dev/null +++ b/.devcontainer/filebrowser/install.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -euo pipefail + +BOLD='\033[0;1m' + +printf "%sInstalling filebrowser\n\n" "${BOLD}" + +# Check if filebrowser is installed. +if ! command -v filebrowser &>/dev/null; then + curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash +fi + +printf "🥳 Installation complete!\n\n" + +# Create run script. +cat >/usr/local/bin/filebrowser-entrypoint <>\${LOG_PATH} 2>&1 & + +printf "📝 Logs at \${LOG_PATH}\n\n" +EOF + +chmod +x /usr/local/bin/filebrowser-entrypoint + +printf "✅ File Browser installed!\n\n" +printf "🚀 Run 'filebrowser-entrypoint' to start the service\n\n" From 5ae320e79e9352e147daf036cdb8d31d1cdddd77 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 26 Jun 2025 16:42:43 +0100 Subject: [PATCH 4/9] fix(agent/agentcontainers): chown coder binary (#18611) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- agent/agentcontainers/api.go | 5 +++++ agent/agentcontainers/api_test.go | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/agent/agentcontainers/api.go b/agent/agentcontainers/api.go index 0f8fd3e380932..dcb9a7b0e2372 100644 --- a/agent/agentcontainers/api.go +++ b/agent/agentcontainers/api.go @@ -1442,6 +1442,11 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c return xerrors.Errorf("set agent binary executable: %w", err) } + // Make sure the agent binary is executable so we can run it. + if _, err := api.ccli.ExecAs(ctx, container.ID, "root", "/bin/sh", "-c", fmt.Sprintf("chown $(id -u):$(id -g) %s", coderPathInsideContainer)); err != nil { + return xerrors.Errorf("set agent binary ownership: %w", err) + } + // Attempt to add CAP_NET_ADMIN to the binary to improve network // performance (optional, allow to fail). See `bootstrap_linux.sh`. // TODO(mafredri): Disable for now until we can figure out why this diff --git a/agent/agentcontainers/api_test.go b/agent/agentcontainers/api_test.go index 33f35e2b21602..7c9b7ce0f632d 100644 --- a/agent/agentcontainers/api_test.go +++ b/agent/agentcontainers/api_test.go @@ -1392,6 +1392,7 @@ func TestAPI(t *testing.T) { mCCLI.EXPECT().ExecAs(gomock.Any(), "test-container-id", "root", "mkdir", "-p", "/.coder-agent").Return(nil, nil), mCCLI.EXPECT().Copy(gomock.Any(), "test-container-id", coderBin, "/.coder-agent/coder").Return(nil), mCCLI.EXPECT().ExecAs(gomock.Any(), "test-container-id", "root", "chmod", "0755", "/.coder-agent", "/.coder-agent/coder").Return(nil, nil), + mCCLI.EXPECT().ExecAs(gomock.Any(), "test-container-id", "root", "/bin/sh", "-c", "chown $(id -u):$(id -g) /.coder-agent/coder").Return(nil, nil), ) mClock.Set(time.Now()).MustWait(ctx) @@ -1480,6 +1481,7 @@ func TestAPI(t *testing.T) { mCCLI.EXPECT().ExecAs(gomock.Any(), "test-container-id", "root", "mkdir", "-p", "/.coder-agent").Return(nil, nil), mCCLI.EXPECT().Copy(gomock.Any(), "test-container-id", coderBin, "/.coder-agent/coder").Return(nil), mCCLI.EXPECT().ExecAs(gomock.Any(), "test-container-id", "root", "chmod", "0755", "/.coder-agent", "/.coder-agent/coder").Return(nil, nil), + mCCLI.EXPECT().ExecAs(gomock.Any(), "test-container-id", "root", "/bin/sh", "-c", "chown $(id -u):$(id -g) /.coder-agent/coder").Return(nil, nil), ) // Verify that the agent has started. @@ -1540,6 +1542,7 @@ func TestAPI(t *testing.T) { mCCLI.EXPECT().ExecAs(gomock.Any(), "new-test-container-id", "root", "mkdir", "-p", "/.coder-agent").Return(nil, nil), mCCLI.EXPECT().Copy(gomock.Any(), "new-test-container-id", coderBin, "/.coder-agent/coder").Return(nil), mCCLI.EXPECT().ExecAs(gomock.Any(), "new-test-container-id", "root", "chmod", "0755", "/.coder-agent", "/.coder-agent/coder").Return(nil, nil), + mCCLI.EXPECT().ExecAs(gomock.Any(), "new-test-container-id", "root", "/bin/sh", "-c", "chown $(id -u):$(id -g) /.coder-agent/coder").Return(nil, nil), ) fakeDCCLI.readConfig.MergedConfiguration.Customizations.Coder = []agentcontainers.CoderCustomization{ @@ -1941,6 +1944,7 @@ func TestAPI(t *testing.T) { mCCLI.EXPECT().ExecAs(gomock.Any(), testContainer.ID, "root", "mkdir", "-p", "/.coder-agent").Return(nil, nil), mCCLI.EXPECT().Copy(gomock.Any(), testContainer.ID, coderBin, "/.coder-agent/coder").Return(nil), mCCLI.EXPECT().ExecAs(gomock.Any(), testContainer.ID, "root", "chmod", "0755", "/.coder-agent", "/.coder-agent/coder").Return(nil, nil), + mCCLI.EXPECT().ExecAs(gomock.Any(), testContainer.ID, "root", "/bin/sh", "-c", "chown $(id -u):$(id -g) /.coder-agent/coder").Return(nil, nil), ) mClock.Set(time.Now()).MustWait(ctx) @@ -2034,6 +2038,7 @@ func TestAPI(t *testing.T) { mCCLI.EXPECT().ExecAs(gomock.Any(), testContainer.ID, "root", "mkdir", "-p", "/.coder-agent").Return(nil, nil), mCCLI.EXPECT().Copy(gomock.Any(), testContainer.ID, coderBin, "/.coder-agent/coder").Return(nil), mCCLI.EXPECT().ExecAs(gomock.Any(), testContainer.ID, "root", "chmod", "0755", "/.coder-agent", "/.coder-agent/coder").Return(nil, nil), + mCCLI.EXPECT().ExecAs(gomock.Any(), testContainer.ID, "root", "/bin/sh", "-c", "chown $(id -u):$(id -g) /.coder-agent/coder").Return(nil, nil), ) mClock.Set(time.Now()).MustWait(ctx) @@ -2138,6 +2143,7 @@ func TestAPI(t *testing.T) { mCCLI.EXPECT().ExecAs(gomock.Any(), testContainer.ID, "root", "mkdir", "-p", "/.coder-agent").Return(nil, nil), mCCLI.EXPECT().Copy(gomock.Any(), testContainer.ID, coderBin, "/.coder-agent/coder").Return(nil), mCCLI.EXPECT().ExecAs(gomock.Any(), testContainer.ID, "root", "chmod", "0755", "/.coder-agent", "/.coder-agent/coder").Return(nil, nil), + mCCLI.EXPECT().ExecAs(gomock.Any(), testContainer.ID, "root", "/bin/sh", "-c", "chown $(id -u):$(id -g) /.coder-agent/coder").Return(nil, nil), ) mClock.Set(time.Now()).MustWait(ctx) From 98c77fece5293f4545b8420b2ee16027ede196d3 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 26 Jun 2025 16:58:10 +0100 Subject: [PATCH 5/9] fix(agent/agentcontainers): stop logging empty lines (#18605) This PR makes the devcontainer logs have fewer whitespace lines. --- agent/agentcontainers/api.go | 2 +- agent/agentcontainers/devcontainercli.go | 2 +- agent/agentcontainers/devcontainercli_test.go | 5 +++++ .../testdata/devcontainercli/parse/up.golden | 15 +-------------- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/agent/agentcontainers/api.go b/agent/agentcontainers/api.go index dcb9a7b0e2372..a78a284293edf 100644 --- a/agent/agentcontainers/api.go +++ b/agent/agentcontainers/api.go @@ -1442,7 +1442,7 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c return xerrors.Errorf("set agent binary executable: %w", err) } - // Make sure the agent binary is executable so we can run it. + // Make sure the agent binary is owned by a valid user so we can run it. if _, err := api.ccli.ExecAs(ctx, container.ID, "root", "/bin/sh", "-c", fmt.Sprintf("chown $(id -u):$(id -g) %s", coderPathInsideContainer)); err != nil { return xerrors.Errorf("set agent binary ownership: %w", err) } diff --git a/agent/agentcontainers/devcontainercli.go b/agent/agentcontainers/devcontainercli.go index 4d3a93ae99f4e..55e4708d46134 100644 --- a/agent/agentcontainers/devcontainercli.go +++ b/agent/agentcontainers/devcontainercli.go @@ -458,7 +458,7 @@ func (l *devcontainerCLILogWriter) Write(p []byte) (n int, err error) { } if logLine.Level >= 3 { l.logger.Info(l.ctx, "@devcontainer/cli", slog.F("line", string(line))) - _, _ = l.writer.Write([]byte(logLine.Text + "\n")) + _, _ = l.writer.Write([]byte(strings.TrimSpace(logLine.Text) + "\n")) continue } // If we've successfully parsed the final log line, it will successfully parse diff --git a/agent/agentcontainers/devcontainercli_test.go b/agent/agentcontainers/devcontainercli_test.go index 523b47d93563c..e3f0445751eb7 100644 --- a/agent/agentcontainers/devcontainercli_test.go +++ b/agent/agentcontainers/devcontainercli_test.go @@ -11,6 +11,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "testing" @@ -343,6 +344,10 @@ func TestDevcontainerCLI_WithOutput(t *testing.T) { t.Run("Up", func(t *testing.T) { t.Parallel() + if runtime.GOOS == "windows" { + t.Skip("Windows uses CRLF line endings, golden file is LF") + } + // Buffers to capture stdout and stderr. outBuf := &bytes.Buffer{} errBuf := &bytes.Buffer{} diff --git a/agent/agentcontainers/testdata/devcontainercli/parse/up.golden b/agent/agentcontainers/testdata/devcontainercli/parse/up.golden index cec57262f790b..022869052cf4b 100644 --- a/agent/agentcontainers/testdata/devcontainercli/parse/up.golden +++ b/agent/agentcontainers/testdata/devcontainercli/parse/up.golden @@ -6,20 +6,14 @@ Run: docker buildx build --load --build-context dev_containers_feature_content_s #0 building with "orbstack" instance using docker driver #1 [internal] load build definition from Dockerfile.extended - #1 transferring dockerfile: 3.09kB done #1 DONE 0.0s #2 resolve image config for docker-image://docker.io/docker/dockerfile:1.4 - #2 DONE 1.3s - - #3 docker-image://docker.io/docker/dockerfile:1.4@sha256:9ba7531bd80fb0a858632727cf7a112fbfd19b17e94c4e84ced81e24ef1a0dbc #3 CACHED - - #4 [internal] load .dockerignore #4 transferring context: 2B done #4 DONE 0.0s @@ -61,17 +55,10 @@ Run: docker buildx build --load --build-context dev_containers_feature_content_s #15 writing image sha256:275dc193c905d448ef3945e3fc86220cc315fe0cb41013988d6ff9f8d6ef2357 done #15 naming to docker.io/library/vsc-devcontainers-template-starter-81d8f17e32abef6d434cbb5a37fe05e5c8a6f8ccede47a61197f002dcbf60566-features done #15 DONE 0.0s - Run: docker buildx build --load --build-context dev_containers_feature_content_source=/var/folders/1y/cm8mblxd7_x9cljwl_jvfprh0000gn/T/devcontainercli/container-features/0.75.0-1744102171193 --build-arg _DEV_CONTAINERS_BASE_IMAGE=mcr.microsoft.com/devcontainers/javascript-node:1-18-bullseye --build-arg _DEV_CONTAINERS_IMAGE_USER=root --build-arg _DEV_CONTAINERS_FEATURE_CONTENT_SOURCE=dev_container_feature_content_temp --target dev_containers_target_stage -f /var/folders/1y/cm8mblxd7_x9cljwl_jvfprh0000gn/T/devcontainercli/container-features/0.75.0-1744102171193/Dockerfile.extended -t vsc-devcontainers-template-starter-81d8f17e32abef6d434cbb5a37fe05e5c8a6f8ccede47a61197f002dcbf60566-features /var/folders/1y/cm8mblxd7_x9cljwl_jvfprh0000gn/T/devcontainercli/empty-folder Run: docker run --sig-proxy=false -a STDOUT -a STDERR --mount type=bind,source=/code/devcontainers-template-starter,target=/workspaces/devcontainers-template-starter,consistency=cached --mount type=volume,src=dind-var-lib-docker-0pctifo8bbg3pd06g3j5s9ae8j7lp5qfcd67m25kuahurel7v7jm,dst=/var/lib/docker -l devcontainer.local_folder=/code/devcontainers-template-starter -l devcontainer.config_file=/code/devcontainers-template-starter/.devcontainer/devcontainer.json --privileged --entrypoint /bin/sh vsc-devcontainers-template-starter-81d8f17e32abef6d434cbb5a37fe05e5c8a6f8ccede47a61197f002dcbf60566-features -c echo Container started Container started - Not setting dockerd DNS manually. - -Running the postCreateCommand from devcontainer.json... - - - +Running the postCreateCommand from devcontainer.json... added 1 package in 784ms - {"outcome":"success","containerId":"bc72db8d0c4c4e941bd9ffc341aee64a18d3397fd45b87cd93d4746150967ba8","remoteUser":"node","remoteWorkspaceFolder":"/workspaces/devcontainers-template-starter"} From 1b1d09158de77cecae3c53df704de40a65839e2c Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Thu, 26 Jun 2025 18:33:44 +0200 Subject: [PATCH 6/9] fix: pin Nix version to 2.28.4 to avoid JSON type error (#18612) Pin Nix version to 2.28.4 in dogfood workflow Pins the Nix version in the dogfood workflow to 2.28.4 to avoid a JSON type error that occurs with Nix 2.29 and above. Change-Id: Ie024d5070dbe5901952fc52463c6602363ef8886 Signed-off-by: Thomas Kosiewski --- .github/workflows/dogfood.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dogfood.yaml b/.github/workflows/dogfood.yaml index b5b2447bd44bf..952e31b7c98ec 100644 --- a/.github/workflows/dogfood.yaml +++ b/.github/workflows/dogfood.yaml @@ -35,7 +35,11 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Nix - uses: nixbuild/nix-quick-install-action@889f3180bb5f064ee9e3201428d04ae9e41d54ad # v31 + uses: nixbuild/nix-quick-install-action@63ca48f939ee3b8d835f4126562537df0fee5b91 # v32 + with: + # Pinning to 2.28 here, as Nix gets a "error: [json.exception.type_error.302] type must be array, but is string" + # on version 2.29 and above. + nix_version: "2.28.4" - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 with: From e03d13211c6e785b676d4099a31dc2646026ee07 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 26 Jun 2025 20:50:53 +0300 Subject: [PATCH 7/9] test(agent): fix TestAgent_DevcontainerRecreate (#18618) --- agent/agent_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/agent_test.go b/agent/agent_test.go index 364ccb0e1abc4..4a9141bd37f9e 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -2372,7 +2372,7 @@ func TestAgent_DevcontainerRecreate(t *testing.T) { // devcontainer, we do it in a goroutine so we can process logs // concurrently. go func(container codersdk.WorkspaceAgentContainer) { - _, err := conn.RecreateDevcontainer(ctx, container.ID) + _, err := conn.RecreateDevcontainer(ctx, devcontainerID.String()) assert.NoError(t, err, "recreate devcontainer should succeed") }(container) From 09cc906981c4fd1dc02f8c5f28228f6ca2f26e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B1=E3=82=A4=E3=83=A9?= Date: Thu, 26 Jun 2025 12:28:00 -0600 Subject: [PATCH 8/9] chore: remove unnecessary redeclarations in for loops (part 2) (#18593) --- agent/agent.go | 1 - agent/agentscripts/agentscripts.go | 2 -- cli/organizationroles.go | 1 - cli/organizationsettings.go | 2 -- cli/server.go | 2 -- coderd/database/pubsub/pubsub_memory.go | 1 - coderd/externalauth/externalauth.go | 2 -- coderd/idpsync/group.go | 4 ---- coderd/rbac/authz.go | 1 - coderd/rbac/roles.go | 1 - coderd/webpush/webpush.go | 1 - enterprise/coderd/proxyhealth/proxyhealth.go | 1 - enterprise/replicasync/replicasync.go | 3 --- site/site.go | 2 -- 14 files changed, 24 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 018b4e0ac29d0..b0a99f118475e 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -565,7 +565,6 @@ func (a *agent) reportMetadata(ctx context.Context, aAPI proto.DRPCAgentClient26 // channel to synchronize the results and avoid both messy // mutex logic and overloading the API. for _, md := range manifest.Metadata { - md := md // We send the result to the channel in the goroutine to avoid // sending the same result multiple times. So, we don't care about // the return values. diff --git a/agent/agentscripts/agentscripts.go b/agent/agentscripts/agentscripts.go index ac6e3a30f44a1..bde3305b15415 100644 --- a/agent/agentscripts/agentscripts.go +++ b/agent/agentscripts/agentscripts.go @@ -149,7 +149,6 @@ func (r *Runner) Init(scripts []codersdk.WorkspaceAgentScript, scriptCompleted S if script.Cron == "" { continue } - script := script _, err := r.cron.AddFunc(script.Cron, func() { err := r.trackRun(r.cronCtx, script, ExecuteCronScripts) if err != nil { @@ -224,7 +223,6 @@ func (r *Runner) Execute(ctx context.Context, option ExecuteOption) error { continue } - script := script eg.Go(func() error { err := r.trackRun(ctx, script, option) if err != nil { diff --git a/cli/organizationroles.go b/cli/organizationroles.go index 4d68ab02ae78d..3651baea88d2f 100644 --- a/cli/organizationroles.go +++ b/cli/organizationroles.go @@ -435,7 +435,6 @@ func applyOrgResourceActions(role *codersdk.Role, resource string, actions []str // Construct new site perms with only new perms for the resource keep := make([]codersdk.Permission, 0) for _, perm := range role.OrganizationPermissions { - perm := perm if string(perm.ResourceType) != resource { keep = append(keep, perm) } diff --git a/cli/organizationsettings.go b/cli/organizationsettings.go index 920ae41ebe1fc..391a4f72e27fd 100644 --- a/cli/organizationsettings.go +++ b/cli/organizationsettings.go @@ -116,7 +116,6 @@ func (r *RootCmd) setOrganizationSettings(orgContext *OrganizationContext, setti } for _, set := range settings { - set := set patch := set.Patch cmd.Children = append(cmd.Children, &serpent.Command{ Use: set.Name, @@ -192,7 +191,6 @@ func (r *RootCmd) printOrganizationSetting(orgContext *OrganizationContext, sett } for _, set := range settings { - set := set fetch := set.Fetch cmd.Children = append(cmd.Children, &serpent.Command{ Use: set.Name, diff --git a/cli/server.go b/cli/server.go index d739863b9fa6f..5074bffc3a342 100644 --- a/cli/server.go +++ b/cli/server.go @@ -1184,7 +1184,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. var wg sync.WaitGroup for i, provisionerDaemon := range provisionerDaemons { id := i + 1 - provisionerDaemon := provisionerDaemon wg.Add(1) go func() { defer wg.Done() @@ -1662,7 +1661,6 @@ func configureServerTLS(ctx context.Context, logger slog.Logger, tlsMinVersion, // Expensively check which certificate matches the client hello. for _, cert := range certs { - cert := cert if err := hi.SupportsCertificate(&cert); err == nil { return &cert, nil } diff --git a/coderd/database/pubsub/pubsub_memory.go b/coderd/database/pubsub/pubsub_memory.go index c4766c3dfa3fb..59a5730ff9808 100644 --- a/coderd/database/pubsub/pubsub_memory.go +++ b/coderd/database/pubsub/pubsub_memory.go @@ -73,7 +73,6 @@ func (m *MemoryPubsub) Publish(event string, message []byte) error { var wg sync.WaitGroup for _, listener := range listeners { wg.Add(1) - listener := listener go func() { defer wg.Done() listener.send(context.Background(), message) diff --git a/coderd/externalauth/externalauth.go b/coderd/externalauth/externalauth.go index 600aacf62f7dd..9b8b87748e784 100644 --- a/coderd/externalauth/externalauth.go +++ b/coderd/externalauth/externalauth.go @@ -505,8 +505,6 @@ func ConvertConfig(instrument *promoauth.Factory, entries []codersdk.ExternalAut ids := map[string]struct{}{} configs := []*Config{} for _, entry := range entries { - entry := entry - // Applies defaults to the config entry. // This allows users to very simply state that they type is "GitHub", // apply their client secret and ID, and have the UI appear nicely. diff --git a/coderd/idpsync/group.go b/coderd/idpsync/group.go index b5d8003165665..0b21c5b9ac84c 100644 --- a/coderd/idpsync/group.go +++ b/coderd/idpsync/group.go @@ -99,7 +99,6 @@ func (s AGPLIDPSync) SyncGroups(ctx context.Context, db database.Store, user dat // membership via the groups the user is in. userOrgs := make(map[uuid.UUID][]database.GetGroupsRow) for _, g := range userGroups { - g := g userOrgs[g.Group.OrganizationID] = append(userOrgs[g.Group.OrganizationID], g) } @@ -337,8 +336,6 @@ func (s GroupSyncSettings) ParseClaims(orgID uuid.UUID, mergedClaims jwt.MapClai groups := make([]ExpectedGroup, 0) for _, group := range parsedGroups { - group := group - // Legacy group mappings happen before the regex filter. mappedGroupName, ok := s.LegacyNameMapping[group] if ok { @@ -355,7 +352,6 @@ func (s GroupSyncSettings) ParseClaims(orgID uuid.UUID, mergedClaims jwt.MapClai mappedGroupIDs, ok := s.Mapping[group] if ok { for _, gid := range mappedGroupIDs { - gid := gid groups = append(groups, ExpectedGroup{OrganizationID: orgID, GroupID: &gid}) } continue diff --git a/coderd/rbac/authz.go b/coderd/rbac/authz.go index a7f77d57ab253..f57ed2585c068 100644 --- a/coderd/rbac/authz.go +++ b/coderd/rbac/authz.go @@ -760,7 +760,6 @@ func rbacTraceAttributes(actor Subject, action policy.Action, objectType string, uniqueRoleNames := actor.SafeRoleNames() roleStrings := make([]string, 0, len(uniqueRoleNames)) for _, roleName := range uniqueRoleNames { - roleName := roleName roleStrings = append(roleStrings, roleName.String()) } return trace.WithAttributes( diff --git a/coderd/rbac/roles.go b/coderd/rbac/roles.go index 9075e7e711adc..ebc7ff8f12070 100644 --- a/coderd/rbac/roles.go +++ b/coderd/rbac/roles.go @@ -845,7 +845,6 @@ func Permissions(perms map[string][]policy.Action) []Permission { list := make([]Permission, 0, len(perms)) for k, actions := range perms { for _, act := range actions { - act := act list = append(list, Permission{ Negate: false, ResourceType: k, diff --git a/coderd/webpush/webpush.go b/coderd/webpush/webpush.go index eb35685402c21..0f54a269cad00 100644 --- a/coderd/webpush/webpush.go +++ b/coderd/webpush/webpush.go @@ -103,7 +103,6 @@ func (n *Webpusher) Dispatch(ctx context.Context, userID uuid.UUID, msg codersdk var mu sync.Mutex var eg errgroup.Group for _, subscription := range subscriptions { - subscription := subscription eg.Go(func() error { // TODO: Implement some retry logic here. For now, this is just a // best-effort attempt. diff --git a/enterprise/coderd/proxyhealth/proxyhealth.go b/enterprise/coderd/proxyhealth/proxyhealth.go index 7faac6a9e8147..ef721841362c8 100644 --- a/enterprise/coderd/proxyhealth/proxyhealth.go +++ b/enterprise/coderd/proxyhealth/proxyhealth.go @@ -240,7 +240,6 @@ func (p *ProxyHealth) runOnce(ctx context.Context, now time.Time) (map[uuid.UUID } // Each proxy needs to have a status set. Make a local copy for the // call to be run async. - proxy := proxy status := ProxyStatus{ Proxy: proxy, CheckedAt: now, diff --git a/enterprise/replicasync/replicasync.go b/enterprise/replicasync/replicasync.go index 0a60ccfd0a1fc..528540a262464 100644 --- a/enterprise/replicasync/replicasync.go +++ b/enterprise/replicasync/replicasync.go @@ -408,9 +408,6 @@ func (m *Manager) AllPrimary() []database.Replica { continue } - // When we assign the non-pointer to a - // variable it loses the reference. - replica := replica replicas = append(replicas, replica) } return replicas diff --git a/site/site.go b/site/site.go index 0a97b4a78d2cb..682d21c695a88 100644 --- a/site/site.go +++ b/site/site.go @@ -849,8 +849,6 @@ func verifyBinSha1IsCurrent(dest string, siteFS fs.FS, shaFiles map[string]strin // Verify the hash of each on-disk binary. for file, hash1 := range shaFiles { - file := file - hash1 := hash1 eg.Go(func() error { hash2, err := sha1HashFile(filepath.Join(dest, file)) if err != nil { From 4f44dd08a97c4d135aa1350c65be314765f19f8e Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 26 Jun 2025 21:30:21 +0300 Subject: [PATCH 9/9] fix(agent/agentcontainers): prevent reassigning proc.agent until successful (#18609) --- agent/agentcontainers/api.go | 5 +++-- agent/agentcontainers/subagent.go | 24 ++++++++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/agent/agentcontainers/api.go b/agent/agentcontainers/api.go index a78a284293edf..26ebafd660fb1 100644 --- a/agent/agentcontainers/api.go +++ b/agent/agentcontainers/api.go @@ -1484,7 +1484,9 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c originalName := subAgentConfig.Name for attempt := 1; attempt <= maxAttemptsToNameAgent; attempt++ { - if proc.agent, err = client.Create(ctx, subAgentConfig); err == nil { + agent, err := client.Create(ctx, subAgentConfig) + if err == nil { + proc.agent = agent // Only reassign on success. if api.usingWorkspaceFolderName[dc.WorkspaceFolder] { api.devcontainerNames[dc.Name] = true delete(api.usingWorkspaceFolderName, dc.WorkspaceFolder) @@ -1492,7 +1494,6 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c break } - // NOTE(DanielleMaywood): // Ordinarily we'd use `errors.As` here, but it didn't appear to work. Not // sure if this is because of the communication protocol? Instead I've opted diff --git a/agent/agentcontainers/subagent.go b/agent/agentcontainers/subagent.go index 42df7080a890a..7d7603feef21d 100644 --- a/agent/agentcontainers/subagent.go +++ b/agent/agentcontainers/subagent.go @@ -188,7 +188,7 @@ func (a *subAgentAPIClient) List(ctx context.Context) ([]SubAgent, error) { return agents, nil } -func (a *subAgentAPIClient) Create(ctx context.Context, agent SubAgent) (SubAgent, error) { +func (a *subAgentAPIClient) Create(ctx context.Context, agent SubAgent) (_ SubAgent, err error) { a.logger.Debug(ctx, "creating sub agent", slog.F("name", agent.Name), slog.F("directory", agent.Directory)) displayApps := make([]agentproto.CreateSubAgentRequest_DisplayApp, 0, len(agent.DisplayApps)) @@ -233,19 +233,27 @@ func (a *subAgentAPIClient) Create(ctx context.Context, agent SubAgent) (SubAgen if err != nil { return SubAgent{}, err } + defer func() { + if err != nil { + // Best effort. + _, _ = a.api.DeleteSubAgent(ctx, &agentproto.DeleteSubAgentRequest{ + Id: resp.GetAgent().GetId(), + }) + } + }() - agent.Name = resp.Agent.Name - agent.ID, err = uuid.FromBytes(resp.Agent.Id) + agent.Name = resp.GetAgent().GetName() + agent.ID, err = uuid.FromBytes(resp.GetAgent().GetId()) if err != nil { - return agent, err + return SubAgent{}, err } - agent.AuthToken, err = uuid.FromBytes(resp.Agent.AuthToken) + agent.AuthToken, err = uuid.FromBytes(resp.GetAgent().GetAuthToken()) if err != nil { - return agent, err + return SubAgent{}, err } - for _, appError := range resp.AppCreationErrors { - app := apps[appError.Index] + for _, appError := range resp.GetAppCreationErrors() { + app := apps[appError.GetIndex()] a.logger.Warn(ctx, "unable to create app", slog.F("agent_name", agent.Name),