feat: auto-assign agents-access role to new users when experiment enabled (#23968)

When the `agents` experiment is enabled, new users are automatically
granted the `agents-access` role at creation time so they can use Coder
Agents without manual admin intervention.

- Auto-assigns in `CreateUser()` — covers admin API, OAuth, and OIDC
creation paths
- Skips auto-assign for OIDC users when enterprise site role sync is
enabled (sync overwrites roles on every login; those admins should use
`--oidc-user-role-default` instead)
- CLI `create-admin-user` bypasses `CreateUser()` but creates `owner`
users who already have all permissions

> 🤖 Written by a Coder Agent. Will be reviewed by a human.
This commit is contained in:
Cian Johnston
2026-04-02 14:46:47 +01:00
committed by GitHub
parent 00217fefa5
commit d4a9c63e91
6 changed files with 101 additions and 13 deletions
+18 -4
View File
@@ -251,10 +251,17 @@ func TestPostChats(t *testing.T) {
_ = createChatModelConfig(t, client)
// Member without agents-access should be denied.
memberClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID)
memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID)
memberClient := codersdk.NewExperimentalClient(memberClientRaw)
_, err := memberClient.CreateChat(ctx, codersdk.CreateChatRequest{
// Strip the auto-assigned agents-access role to test
// the denied case.
_, err := client.Client.UpdateUserRoles(ctx, member.Username, codersdk.UpdateRoles{
Roles: []string{},
})
require.NoError(t, err)
_, err = memberClient.CreateChat(ctx, codersdk.CreateChatRequest{
Content: []codersdk.ChatInputPart{
{
Type: codersdk.ChatInputPartTypeText,
@@ -264,7 +271,6 @@ func TestPostChats(t *testing.T) {
})
requireSDKError(t, err, http.StatusForbidden)
})
t.Run("HidesSystemPromptMessages", func(t *testing.T) {
t.Parallel()
@@ -628,7 +634,15 @@ func TestListChats(t *testing.T) {
// returning empty because no chats exist.
memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID)
memberClient := codersdk.NewExperimentalClient(memberClientRaw)
_, err := db.InsertChat(dbauthz.AsSystemRestricted(ctx), database.InsertChatParams{
// Strip the auto-assigned agents-access role to test
// the denied case.
_, err := client.Client.UpdateUserRoles(ctx, member.Username, codersdk.UpdateRoles{
Roles: []string{},
})
require.NoError(t, err)
_, err = db.InsertChat(dbauthz.AsSystemRestricted(ctx), database.InsertChatParams{
Status: database.ChatStatusWaiting,
OwnerID: member.ID,
LastModelConfigID: modelConfig.ID,
+12
View File
@@ -1619,6 +1619,18 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create
rbacRoles = req.RBACRoles
}
// When the agents experiment is enabled, auto-assign the
// agents-access role so new users can use Coder Agents
// without manual admin intervention. Skip this for OIDC
// users when site role sync is enabled, because the sync
// will overwrite roles on every login anyway — those
// admins should use --oidc-user-role-default instead.
if api.Experiments.Enabled(codersdk.ExperimentAgents) &&
!(req.LoginType == database.LoginTypeOIDC && api.IDPSync.SiteRoleSyncEnabled()) &&
!slices.Contains(rbacRoles, codersdk.RoleAgentsAccess) {
rbacRoles = append(rbacRoles, codersdk.RoleAgentsAccess)
}
var user database.User
err := store.InTx(func(tx database.Store) error {
orgRoles := make([]string, 0)
+29
View File
@@ -758,6 +758,35 @@ func TestPostUsers(t *testing.T) {
assert.Equal(t, firstUser.OrganizationID, user.OrganizationIDs[0])
})
// CreateWithAgentsExperiment verifies that new users
// are auto-assigned the agents-access role when the
// experiment is enabled. The experiment-disabled case
// is implicitly covered by TestInitialRoles, which
// asserts exactly [owner] with no experiment — it
// would fail if agents-access leaked through.
t.Run("CreateWithAgentsExperiment", func(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentAgents)}
client := coderdtest.New(t, &coderdtest.Options{DeploymentValues: dv})
firstUser := coderdtest.CreateFirstUser(t, client)
ctx := testutil.Context(t, testutil.WaitLong)
user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
Email: "another@user.org",
Username: "someone-else",
Password: "SomeSecurePassword!",
})
require.NoError(t, err)
roles, err := client.UserRoles(ctx, user.Username)
require.NoError(t, err)
require.Contains(t, roles.Roles, codersdk.RoleAgentsAccess,
"new user should have agents-access role when agents experiment is enabled")
})
t.Run("CreateWithStatus", func(t *testing.T) {
t.Parallel()
auditor := audit.NewMock()
+6 -3
View File
@@ -65,9 +65,12 @@ Once the server restarts with the experiment enabled:
1. Navigate to the **Agents** page in the Coder dashboard.
1. Open **Admin** settings and configure at least one LLM provider and model.
See [Models](./models.md) for detailed setup instructions.
1. Grant the **Coder Agents User** role to users who need to create chats.
Go to **Admin** > **Users**, click the roles icon next to each user,
and enable **Coder Agents User**.
1. Grant the **Coder Agents User** role to existing users who need to create
chats. New users receive the role automatically. For existing users, go to
**Admin** > **Users**, click the roles icon next to each user, and enable
**Coder Agents User**. See
[Grant Coder Agents User](./getting-started.md#step-3-grant-coder-agents-user)
for a bulk CLI option.
1. Developers can then start a new chat from the Agents page.
## Licensing and availability
+35 -6
View File
@@ -24,8 +24,9 @@ Before you begin, confirm the following:
for the agent to select when provisioning workspaces.
- **Admin access** to the Coder deployment for enabling the experiment and
configuring providers.
- **Coder Agents User role** assigned to each user who needs to interact with Coder Agents.
Owners can assign this from **Admin** > **Users**. See
- **Coder Agents User role** is automatically assigned to new users when the
`agents` experiment is enabled. For existing users, owners can assign it from
**Admin** > **Users**. See
[Grant Coder Agents User](#step-3-grant-coder-agents-user) below.
## Step 1: Enable the experiment
@@ -74,15 +75,43 @@ Detailed instructions for each provider and model option are in the
## Step 3: Grant Coder Agents User
The **Coder Agents User** role controls which users can interact with Coder Agents.
Members do not have Coder Agents User by default.
The **Coder Agents User** role controls which users can interact with
Coder Agents.
### New users
When the `agents` experiment is enabled, new users are automatically
assigned the **Coder Agents User** role at account creation. No admin
action is required.
### Existing users
Users who were created before the experiment was enabled do not receive
the role automatically. Owners can assign it from the dashboard or in
bulk via the CLI.
**Dashboard (individual):**
1. Go to **Admin** > **Users** in the Coder dashboard.
1. Click the roles icon next to the user you want to grant access to.
1. Enable the **Coder Agents User** role and save.
Repeat for each user who needs access. Owners always have full access
and do not need the role.
**CLI (bulk):**
To grant the role to all active users at once:
```sh
coder users list --status active -o json \
| jq -r '.[].username' \
| while read u; do
coder users edit-roles "$u" \
--roles "$(coder users show "$u" -o json \
| jq -r '[.roles[].name, "agents-access"] | unique | join(",")')" \
--yes
done
```
Owners always have full access and do not need the role.
> [!NOTE]
> Users who created conversations before this role was introduced are
+1
View File
@@ -291,6 +291,7 @@ func TestUserOIDC(t *testing.T) {
},
DeploymentValues: func(dv *codersdk.DeploymentValues) {
dv.OIDC.UserRoleField = "roles"
dv.Experiments = []string{string(codersdk.ExperimentAgents)}
},
})