From 78b5a0f5a2f408a6f394e1976f6aac6c8ce23e1a Mon Sep 17 00:00:00 2001
From: Ethan <39577870+ethanndickson@users.noreply.github.com>
Date: Thu, 6 Aug 2026 14:45:38 +1000
Subject: [PATCH] feat(cli): add --agents-allowed to template commands (#27517)
Relates to CODAGT-713
Depends on #27515
This adds `--agents-allowed` to `coder templates create` and `coder templates edit`. Template creation defaults the option to true, matching the per-template API and database default, while template editing only changes the value when the flag is explicitly supplied so unrelated edits preserve the existing setting.
The generated CLI help and reference documentation include the new option. #27518 updates the Coder Agents platform controls documentation to describe the completed per-template model.
---
cli/templatecreate.go | 8 +++
cli/templatecreate_test.go | 51 ++++++++++++++
cli/templateedit.go | 12 ++++
cli/templateedit_test.go | 70 +++++++++++++++++++
.../coder_templates_create_--help.golden | 3 +
.../coder_templates_edit_--help.golden | 3 +
docs/reference/cli/templates_create.md | 9 +++
docs/reference/cli/templates_edit.md | 9 +++
8 files changed, 165 insertions(+)
diff --git a/cli/templatecreate.go b/cli/templatecreate.go
index d1bc545181..62ba06dde1 100644
--- a/cli/templatecreate.go
+++ b/cli/templatecreate.go
@@ -21,6 +21,7 @@ func (r *RootCmd) templateCreate() *serpent.Command {
provisionerTags []string
variablesFile string
commandLineVariables []string
+ agentsAllowed bool
disableEveryone bool
requireActiveVersion bool
@@ -159,6 +160,7 @@ func (r *RootCmd) templateCreate() *serpent.Command {
TimeTilDormantAutoDeleteMillis: ptr.Ref(dormancyAutoDeletion.Milliseconds()),
DisableEveryoneGroupAccess: disableEveryone,
RequireActiveVersion: requireActiveVersion,
+ AgentsAllowed: &agentsAllowed,
}
template, err := client.CreateTemplate(inv.Context(), organization.ID, createReq)
@@ -179,6 +181,12 @@ func (r *RootCmd) templateCreate() *serpent.Command {
},
}
cmd.Options = serpent.OptionSet{
+ {
+ Flag: "agents-allowed",
+ Description: "Allow Coder Agents to create workspaces using this template.",
+ Default: "true",
+ Value: serpent.BoolOf(&agentsAllowed),
+ },
{
Flag: "private",
Description: "Disable the default behavior of granting template access to the 'everyone' group. " +
diff --git a/cli/templatecreate_test.go b/cli/templatecreate_test.go
index cb74480043..36c9eff055 100644
--- a/cli/templatecreate_test.go
+++ b/cli/templatecreate_test.go
@@ -12,6 +12,7 @@ import (
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
+ "github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil"
@@ -58,6 +59,56 @@ func TestCliTemplateCreate(t *testing.T) {
}
}
})
+ t.Run("AgentsAllowed", func(t *testing.T) {
+ t.Parallel()
+
+ client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
+ owner := coderdtest.CreateFirstUser(t, client)
+ templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
+
+ for _, tt := range []struct {
+ name string
+ flag string
+ agentsAllowed bool
+ }{
+ {
+ name: "DefaultTrue",
+ agentsAllowed: true,
+ },
+ {
+ name: "False",
+ flag: "--agents-allowed=false",
+ agentsAllowed: false,
+ },
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ templateName := coderdtest.RandomUsername(t)
+ source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
+ args := []string{
+ "templates",
+ "create",
+ templateName,
+ "--yes",
+ "--directory", source,
+ "--test.provisioner", string(database.ProvisionerTypeEcho),
+ }
+ if tt.flag != "" {
+ args = append(args, tt.flag)
+ }
+ inv, root := clitest.New(t, args...)
+ clitest.SetupConfig(t, templateAdmin, root)
+
+ require.NoError(t, inv.Run())
+
+ template, err := client.TemplateByName(t.Context(), owner.OrganizationID, templateName)
+ require.NoError(t, err)
+ require.Equal(t, tt.agentsAllowed, template.AgentsAllowed)
+ })
+ }
+ })
+
t.Run("CreateNoLockfile", func(t *testing.T) {
t.Parallel()
logger := testutil.Logger(t)
diff --git a/cli/templateedit.go b/cli/templateedit.go
index e25da3462c..751bb63304 100644
--- a/cli/templateedit.go
+++ b/cli/templateedit.go
@@ -33,6 +33,7 @@ func (r *RootCmd) templateEdit() *serpent.Command {
allowUserCancelWorkspaceJobs bool
allowUserAutostart bool
allowUserAutostop bool
+ agentsAllowed bool
requireActiveVersion bool
deprecationMessage string
disableEveryone bool
@@ -142,6 +143,10 @@ func (r *RootCmd) templateEdit() *serpent.Command {
dormancyAutoDeletion = time.Duration(template.TimeTilDormantAutoDeleteMillis) * time.Millisecond
}
+ if !userSetOption(inv, "agents-allowed") {
+ agentsAllowed = template.AgentsAllowed
+ }
+
if !userSetOption(inv, "require-active-version") {
requireActiveVersion = template.RequireActiveVersion
}
@@ -199,6 +204,7 @@ func (r *RootCmd) templateEdit() *serpent.Command {
AllowUserCancelWorkspaceJobs: &allowUserCancelWorkspaceJobs,
AllowUserAutostart: &allowUserAutostart,
AllowUserAutostop: &allowUserAutostop,
+ AgentsAllowed: &agentsAllowed,
RequireActiveVersion: &requireActiveVersion,
DeprecationMessage: deprecated,
DisableEveryoneGroupAccess: &disableEveryoneGroup,
@@ -292,6 +298,12 @@ func (r *RootCmd) templateEdit() *serpent.Command {
Default: "0h",
Value: serpent.DurationOf(&dormancyAutoDeletion),
},
+ {
+ Flag: "agents-allowed",
+ Description: "Allow Coder Agents to create workspaces using this template.",
+ Default: "true",
+ Value: serpent.BoolOf(&agentsAllowed),
+ },
{
Flag: "allow-user-cancel-workspace-jobs",
Description: "Allow users to cancel in-progress workspace jobs.",
diff --git a/cli/templateedit_test.go b/cli/templateedit_test.go
index d6c8af82b0..3acc6513da 100644
--- a/cli/templateedit_test.go
+++ b/cli/templateedit_test.go
@@ -115,6 +115,76 @@ func TestTemplateEdit(t *testing.T) {
assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
assert.Equal(t, template.AllowUserCancelWorkspaceJobs, updated.AllowUserCancelWorkspaceJobs)
})
+ t.Run("AgentsAllowed", func(t *testing.T) {
+ t.Parallel()
+
+ client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
+ owner := coderdtest.CreateFirstUser(t, client)
+ templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
+
+ for _, tt := range []struct {
+ name string
+ initialAgentsAllowed bool
+ flag string
+ description string
+ wantAgentsAllowed bool
+ }{
+ {
+ name: "ExplicitTrue",
+ initialAgentsAllowed: false,
+ flag: "--agents-allowed=true",
+ wantAgentsAllowed: true,
+ },
+ {
+ name: "ExplicitFalse",
+ initialAgentsAllowed: true,
+ flag: "--agents-allowed=false",
+ wantAgentsAllowed: false,
+ },
+ {
+ name: "OmittedPreservesTrue",
+ initialAgentsAllowed: true,
+ description: "updated description",
+ wantAgentsAllowed: true,
+ },
+ {
+ name: "OmittedPreservesFalse",
+ initialAgentsAllowed: false,
+ description: "updated description",
+ wantAgentsAllowed: false,
+ },
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
+ _ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
+ template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(req *codersdk.CreateTemplateRequest) {
+ req.AgentsAllowed = &tt.initialAgentsAllowed
+ })
+
+ cmdArgs := []string{"templates", "edit", template.Name}
+ if tt.flag != "" {
+ cmdArgs = append(cmdArgs, tt.flag)
+ }
+ if tt.description != "" {
+ cmdArgs = append(cmdArgs, "--description", tt.description)
+ }
+ inv, root := clitest.New(t, cmdArgs...)
+ clitest.SetupConfig(t, templateAdmin, root)
+
+ require.NoError(t, inv.Run())
+
+ updated, err := client.Template(t.Context(), template.ID)
+ require.NoError(t, err)
+ require.Equal(t, tt.wantAgentsAllowed, updated.AgentsAllowed)
+ if tt.description != "" {
+ require.Equal(t, tt.description, updated.Description)
+ }
+ })
+ }
+ })
+
t.Run("InvalidDisplayName", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
diff --git a/cli/testdata/coder_templates_create_--help.golden b/cli/testdata/coder_templates_create_--help.golden
index c0370d93d2..7ef71d7d1c 100644
--- a/cli/testdata/coder_templates_create_--help.golden
+++ b/cli/testdata/coder_templates_create_--help.golden
@@ -10,6 +10,9 @@ OPTIONS:
-O, --org string, $CODER_ORGANIZATION
Select which organization (uuid or name) to use.
+ --agents-allowed bool (default: true)
+ Allow Coder Agents to create workspaces using this template.
+
--default-ttl duration (default: 24h)
Specify a default TTL for workspaces created from this template. It is
the default time before shutdown - workspaces created from this
diff --git a/cli/testdata/coder_templates_edit_--help.golden b/cli/testdata/coder_templates_edit_--help.golden
index 73760dadfc..972437a8e8 100644
--- a/cli/testdata/coder_templates_edit_--help.golden
+++ b/cli/testdata/coder_templates_edit_--help.golden
@@ -14,6 +14,9 @@ OPTIONS:
template will have their shutdown time bumped by this value when
activity is detected. Maps to "Activity bump" in the UI.
+ --agents-allowed bool (default: true)
+ Allow Coder Agents to create workspaces using this template.
+
--allow-user-autostart bool (default: true)
Allow users to configure autostart for workspaces on this template.
This can only be disabled in enterprise.
diff --git a/docs/reference/cli/templates_create.md b/docs/reference/cli/templates_create.md
index 3f46f3e759..a3bba84d92 100644
--- a/docs/reference/cli/templates_create.md
+++ b/docs/reference/cli/templates_create.md
@@ -11,6 +11,15 @@ coder templates create [flags] [name]
## Options
+### --agents-allowed
+
+| | |
+|---------|-------------------|
+| Type | bool |
+| Default | true |
+
+Allow Coder Agents to create workspaces using this template.
+
### --private
| | |
diff --git a/docs/reference/cli/templates_edit.md b/docs/reference/cli/templates_edit.md
index 5e9fe47705..2e472d1600 100644
--- a/docs/reference/cli/templates_edit.md
+++ b/docs/reference/cli/templates_edit.md
@@ -126,6 +126,15 @@ Specify a duration workspaces may be inactive prior to being moved to the dorman
Specify a duration workspaces may be in the dormant state prior to being deleted. This licensed feature's default is 0h (off). Maps to "Dormancy Auto-Deletion" in the UI.
+### --agents-allowed
+
+| | |
+|---------|-------------------|
+| Type | bool |
+| Default | true |
+
+Allow Coder Agents to create workspaces using this template.
+
### --allow-user-cancel-workspace-jobs
| | |