CodeBuild Spec Sandboxes for AI Coding Agents: Ephemeral Builds Without ECS Sprawl

0 views

Your agent wants to run npm test on a PR branch before it proposes a merge. You already have ephemeral ECS Fargate sandboxes for arbitrary tool execution — and they are the right hammer for long-lived interactive shells. For CI-shaped tool calls (lint, unit tests, typecheck, build artifacts), standing up another Fargate task family per language is sprawl. The unfair advantage is a CodeBuild project as a sandbox: buildspec-driven, privilegedMode: false by default, artifacts to S3, IAM scoped to one repo bucket, billed per build-minute.

⚡ TL;DR: Use CodeBuild when the agent tool is “run this buildspec on commit SHA X.” Keep ECS Fargate for interactive / custom sandboxes covered in Secure AI Sandboxes and LLM Coding Agents Lambda Sandboxes. Gate hallucinated AWS changes with Cursor Background Agents CI Gates. Never privileged: true unless you truly need Docker-in-Docker — and even then prefer CodeBuild’s managed images carefully.

When CodeBuild beats ECS for agent tools

Signal Prefer CodeBuild Prefer ECS Fargate
Work is lint/test/build
Need interactive shell / RDP-like session
Want buildspec + CloudWatch Logs groups for free
Custom long-running sidecar mesh
Cost for 2–10 min CI-like bursts ✅ often cheaper cold start + task overhead
Already covered Fargate patterns elsewhere Use this post See sandbox post

CodeBuild is build-centric: source in, commands, artifacts out. That matches how coding agents validate patches.

Project shape: buildspec, not a snowflake container

# buildspec.yml — checked into the repo the agent is patching
version: 0.2
env:
  variables:
    NODE_OPTIONS: "--max-old-space-size=2048"
  # ❌ Do not put SaaS tokens in plaintext env here — use Secrets Manager
  # secrets-manager:
  #   GITHUB_TOKEN: agent/$TENANT/github_pr_read:token
phases:
  install:
    runtime-versions:
      nodejs: 20
    commands:
      - npm ci --ignore-scripts
  pre_build:
    commands:
      - npm run lint
  build:
    commands:
      - npm test -- --ci
      - npm run typecheck
artifacts:
  files:
    - "coverage/**/*"
    - "junit.xml"
  name: agent-build-$(date +%Y%m%d%H%M%S)

✅ Agent tool submits sourceVersion = commit SHA or PR ref; it does not invent shell scripts ad hoc when a buildspec already defines the contract.

Start builds from the agent tool (TypeScript)

import {
  CodeBuildClient,
  StartBuildCommand,
  BatchGetBuildsCommand,
} from "@aws-sdk/client-codebuild";

const cb = new CodeBuildClient({});

export async function runPrSandboxBuild(input: {
  projectName: string;
  sourceVersion: string; // commit SHA
  tenantId: string;
  idempotencyKey: string;
}) {
  // ✅ Idempotency: if you already started this key, return prior build id
  const start = await cb.send(
    new StartBuildCommand({
      projectName: input.projectName,
      sourceVersion: input.sourceVersion,
      environmentVariablesOverride: [
        { name: "TENANT_ID", value: input.tenantId, type: "PLAINTEXT" },
        {
          name: "IDEMPOTENCY_KEY",
          value: input.idempotencyKey,
          type: "PLAINTEXT",
        },
      ],
      // Optional: buildspec override only for allowlisted extra steps
    })
  );

  const buildId = start.build?.id;
  if (!buildId) throw new Error("no_build_id");

  // Poll with timeout — do not hold a Bedrock turn open forever;
  // prefer async tool + EventBridge like agent async tools pattern
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const got = await cb.send(
      new BatchGetBuildsCommand({ ids: [buildId] })
    );
    const build = got.builds?.[0];
    const status = build?.buildStatus;
    if (status && status !== "IN_PROGRESS") {
      return {
        buildId,
        status,
        logLink: build?.logs?.deepLink,
        artifacts: build?.artifacts?.location,
      };
    }
  }
  return { buildId, status: "TIMEOUT_WAITING", artifacts: null };
}

For multi-turn agents, prefer async: StartBuild → EventBridge rule on CodeBuild state change → resume agent session (same idea as Bedrock Agents + EventBridge async tools).

IAM role scoped to the repo bucket

The CodeBuild service role should not be the agent runtime role.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadOnlySourceBucket",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:GetObjectVersion"],
      "Resource": "arn:aws:s3:::acme-agent-src/tenant-${tenant}/*"
    },
    {
      "Sid": "WriteArtifactsPrefix",
      "Effect": "Allow",
      "Action": ["s3:PutObject"],
      "Resource": "arn:aws:s3:::acme-agent-artifacts/tenant-${tenant}/*"
    },
    {
      "Sid": "Logs",
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:log-group:/aws/codebuild/agent-*"
    },
    {
      "Sid": "DenyPrivilegeEscalation",
      "Effect": "Deny",
      "Action": [
        "iam:PassRole",
        "sts:AssumeRole",
        "ec2:Create*",
        "lambda:Create*",
        "sagemaker:*"
      ],
      "Resource": "*"
    }
  ]
}

Agent runtime IAM may only codebuild:StartBuild / BatchGetBuilds on the specific project ARN, with tags — see IAM Condition Keys for Agent Runtimes.

Privileged false, VPC optional

// CDK-ish environment block
environment: {
  buildImage: codebuild.LinuxBuildImage.STANDARD_7_0,
  privileged: false, // ✅ default for agent sandboxes
  computeType: codebuild.ComputeType.SMALL,
  // vpc: optional — only if tests need private RDS / internal npm
}

❌ Patterns:

  • privileged: true “because npm might need Docker” — use a prebuilt image or CodeBuild’s Docker support only with a dedicated project and tighter controls
  • Agent-supplied buildspecOverride that runs curl | bash from a model-generated URL
  • Shared project across tenants without prefix isolation on source/artifact paths

✅ Allowlist buildspec overrides to a few static snippets you own; prefer repo buildspec at the commit SHA the agent is testing.

Compare briefly to ECS Fargate sandboxes

ECS Fargate sandboxes shine when the agent needs a mutable workspace, package installs outside a fixed buildspec, or binary tools you do not want in CodeBuild images. They cost more operational surface (task defs, networking, teardown watchdogs). CodeBuild gives you:

  • Managed images and log streaming
  • Native artifact export
  • Per-build isolation without a task-def zoo

Use Lambda sandboxes for tiny ephemeral snippets; CodeBuild for repo-scale CI; ECS for interactive/custom. Do not collapse all three into one “agent runner.”

Debugging agent-triggered builds

  • Always return logs.deepLink to the agent tool result (redact secrets in build logs separately).
  • On FAULT / TIMED_OUT, surface buildStatus + phase failure — models otherwise retry forever.
  • Cap concurrent builds per tenant (CodeBuild account limits + your own DynamoDB semaphore).
  • Store junit/coverage artifacts and let a second tool summarize — do not dump megabyte logs into the Bedrock context.
// ✅ Tool result stays small
return {
  status: "FAILED",
  failedPhase: "BUILD",
  summary: "3 tests failed in auth.test.ts",
  logLink,
  artifactUri,
};

Checklist

  • [ ] CI-like agent tools use CodeBuild; interactive shells stay on ECS
  • [ ] privilegedMode: false unless a dedicated hardened project requires otherwise
  • [ ] Service role scoped to tenant-prefixed source + artifact buckets
  • [ ] Agent role can StartBuild only on named projects (tag conditions)
  • [ ] No model-controlled arbitrary buildspec from untrusted URLs
  • [ ] Prefer async StartBuild + EventBridge resume over long polling in a Converse turn
  • [ ] Idempotency key per (tenant, SHA, suite) to prevent build storms
  • [ ] Tool results return status + log link + short summary, not full logs

CodeBuild will not replace every sandbox. It will replace the expensive habit of launching Fargate every time an agent only needed npm test on a SHA.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a comment

No account needed. Name and email are optional.