Skip to content

feat(agent): disable devcontainers for sub agents #18303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,18 @@ func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context,
if manifest.AgentID == uuid.Nil {
return xerrors.New("nil agentID returned by manifest")
}
if manifest.ParentID != uuid.Nil {
// This is a sub agent, disable all the features that should not
// be used by sub agents.
a.logger.Debug(ctx, "sub agent detected, disabling features",
slog.F("parent_id", manifest.ParentID),
slog.F("agent_id", manifest.AgentID),
)
if a.experimentalDevcontainersEnabled {
a.logger.Info(ctx, "devcontainers are not supported on sub agents, disabling feature")
a.experimentalDevcontainersEnabled = false
}
}
a.client.RewriteDERPMap(manifest.DERPMap)

// Expand the directory and send it back to coderd so external
Expand Down
28 changes: 28 additions & 0 deletions agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2423,6 +2423,34 @@ waitForOutcomeLoop:
}(container)
}

func TestAgent_DevcontainersDisabledForSubAgent(t *testing.T) {
t.Parallel()

// Create a manifest with a ParentID to make this a sub agent.
manifest := agentsdk.Manifest{
AgentID: uuid.New(),
ParentID: uuid.New(),
}

// Setup the agent with devcontainers enabled initially.
//nolint:dogsled
conn, _, _, _, _ := setupAgent(t, manifest, 0, func(_ *agenttest.Client, o *agent.Options) {
o.ExperimentalDevcontainersEnabled = true
})

// Query the containers API endpoint. This should fail because
// devcontainers have been disabled for the sub agent.
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
defer cancel()

_, err := conn.ListContainers(ctx)
require.Error(t, err)

// Verify the error message contains the expected text.
require.Contains(t, err.Error(), "The agent dev containers feature is experimental and not enabled by default.")
require.Contains(t, err.Error(), "To enable this feature, set CODER_AGENT_DEVCONTAINERS_ENABLE=true in your template.")
}

func TestAgent_Dial(t *testing.T) {
t.Parallel()

Expand Down
1 change: 1 addition & 0 deletions codersdk/agentsdk/agentsdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ type PostMetadataRequest struct {
type PostMetadataRequestDeprecated = codersdk.WorkspaceAgentMetadataResult

type Manifest struct {
ParentID uuid.UUID `json:"parent_id"`
AgentID uuid.UUID `json:"agent_id"`
AgentName string `json:"agent_name"`
// OwnerUsername and WorkspaceID are used by an open-source user to identify the workspace.
Expand Down
10 changes: 10 additions & 0 deletions codersdk/agentsdk/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ import (
)

func ManifestFromProto(manifest *proto.Manifest) (Manifest, error) {
parentID := uuid.Nil
if pid := manifest.GetParentId(); pid != nil {
var err error
parentID, err = uuid.FromBytes(pid)
if err != nil {
return Manifest{}, xerrors.Errorf("error converting workspace agent parent ID: %w", err)
}
}
apps, err := AppsFromProto(manifest.Apps)
if err != nil {
return Manifest{}, xerrors.Errorf("error converting workspace agent apps: %w", err)
Expand All @@ -36,6 +44,7 @@ func ManifestFromProto(manifest *proto.Manifest) (Manifest, error) {
return Manifest{}, xerrors.Errorf("error converting workspace agent devcontainers: %w", err)
}
return Manifest{
ParentID: parentID,
AgentID: agentID,
AgentName: manifest.AgentName,
OwnerName: manifest.OwnerUsername,
Expand All @@ -62,6 +71,7 @@ func ProtoFromManifest(manifest Manifest) (*proto.Manifest, error) {
return nil, xerrors.Errorf("convert workspace apps: %w", err)
}
return &proto.Manifest{
ParentId: manifest.ParentID[:],
AgentId: manifest.AgentID[:],
AgentName: manifest.AgentName,
OwnerUsername: manifest.OwnerName,
Expand Down
2 changes: 2 additions & 0 deletions codersdk/agentsdk/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
func TestManifest(t *testing.T) {
t.Parallel()
manifest := agentsdk.Manifest{
ParentID: uuid.New(),
AgentID: uuid.New(),
AgentName: "test-agent",
OwnerName: "test-owner",
Expand Down Expand Up @@ -142,6 +143,7 @@ func TestManifest(t *testing.T) {
require.NoError(t, err)
back, err := agentsdk.ManifestFromProto(p)
require.NoError(t, err)
require.Equal(t, manifest.ParentID, back.ParentID)
require.Equal(t, manifest.AgentID, back.AgentID)
require.Equal(t, manifest.AgentName, back.AgentName)
require.Equal(t, manifest.OwnerName, back.OwnerName)
Expand Down
Loading