Skip to content

Instantly share code, notes, and snippets.

@christianalfoni
Created August 9, 2025 11:09
Show Gist options
  • Save christianalfoni/497e4cd7cef2aadc6621f18a9242db67 to your computer and use it in GitHub Desktop.
Save christianalfoni/497e4cd7cef2aadc6621f18a9242db67 to your computer and use it in GitHub Desktop.

Technical Specifications: "Pint" Sandbox Type with Feature Flag Support

Overview

This specification outlines the implementation of a new sandbox type called "pint" in the CodeSandbox platform. The feature enables passing a feature flag from the CodeSandbox SDK through the server to the pitcher-manager during sandbox creation and VM startup processes.

Technical Requirements

Core Requirements

  • Add support for a new sandbox type identifier: "pint"
  • Implement feature flag propagation from SDK to server to pitcher-manager
  • Ensure backward compatibility with existing sandbox creation flows
  • Store feature flag information in sandbox records for persistence

Performance Requirements

  • Feature flag checking should not add significant latency to sandbox creation
  • Flag should be efficiently passed through the entire request flow
  • No impact on existing sandbox types' performance

Security Requirements

  • Feature flag should only be accessible to authorized users/teams
  • No sensitive information exposure through feature flag
  • Standard authorization flows must be maintained

Architecture and Design

Component Architecture

CodeSandbox SDK → CodeSandbox Server → Pitcher Manager
     ↓                    ↓                  ↓
Feature Flag        Store Flag         Use Flag for
Detection          in Database        Container Setup

Data Flow

  1. SDK Layer: Accept optional feature flag parameter in sandbox creation requests
  2. Server Layer: Validate, store, and forward feature flag to pitcher-manager
  3. Pitcher Manager: Receive flag and apply pint-specific container configuration

Database Schema Changes

Sandboxes Table

Add new column to store the sandbox type:

ALTER TABLE sandboxes ADD COLUMN sandbox_type VARCHAR(50) DEFAULT 'standard';
CREATE INDEX idx_sandboxes_sandbox_type ON sandboxes(sandbox_type);

Alternative: Feature Flags Table

If more granular control is needed:

CREA
TE TABLE sandbox_feature_flags (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  sandbox_id UUID NOT NULL REFERENCES sandboxes(id),
  flag_name VARCHAR(255) NOT NULL,
  flag_value BOOLEAN DEFAULT FALSE,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_sandbox_feature_flags_sandbox_id ON sandbox_feature_flags(sandbox_id);
CREATE INDEX idx_sandbox_feature_flags_flag_name ON sandbox_feature_flags(flag_name);

Implementation Approach

1. CodeSandbox SDK Changes

File: src/types.ts

Add feature flag support to sandbox creation options:

export interface CreateSandboxOpts extends CreateSandboxBaseOpts {
  id?: string;
  /**
   * Feature flags for experimental sandbox types
   */
  featureFlags?: {
    sandboxType?: 'standard' | 'pint';
    [key: string]: any;
  };
}

export interface StartSandboxOpts {
  // ... existing properties
  /**
   * Feature flags for experimental features
   */
  featureFlags?: {
    sandboxType?: 'standard' | 'pint';
    [key: string]: any;
  };
}

File: src/Sandboxes.ts

Update the create method to handle feature flags:

private async createTemplateSandbox(
  opts?: CreateSandboxOpts & StartSandboxOpts
) {
  // ... existing code
  
  const startResponse = await this.api.startVm(
    sandbox.id,
    getStartOptions(opts, {
      featureFlags: opts?.featureFlags
    })
  );

  return new Sandbox(sandbox.id, this.api, startResponse, this.tracer);
}

File: src/utils/api.ts

Update getStartOptions to include feature flags:

export function getStartOptions(opts?: StartSandboxOpts, additional?: any): any {
  return {
    tier: opts?.vmTier?.name,
    hibernation_timeout_seconds: opts?.hibernationTimeoutSeconds,
    ipcountry: opts?.ipcountry,
    automatic_wakeup_config: opts?.automaticWakeupConfig,
    feature_flags: {
      ...opts?.featureFlags,
      ...additional?.featureFlags
    }
  };
}

2. CodeSandbox Server Changes

File: lib/code_sandbox/sandboxes/sandbox.ex

Add sandbox_type field to schema:

schema "sandboxes" do
  # ... existing fields
  field :sandbox_type, :string, default: "standard"
  # ... rest of schema
end

def changeset(sandbox, attrs) do
  sandbox
  |> cast(attrs, [..., :sandbox_type])
  |> validate_inclusion(:sandbox_type, ["standard", "pint"])
  # ... rest of changeset
end

File: lib/code_sandbox_api/controllers/vm_controller.ex

Update start action to handle feature flags:

def start(conn, _params) do
  team = current_team(conn)
  scopes = current_scopes(conn)
  params = conn.body_params

  with {:sandbox, %Sandbox{v2: true} = sandbox} <-
         {:sandbox, Sandboxes.get_by_shortid(conn.path_params["id"], preload: [:team])},
       {:authorization, authorization} when authorization in [:read, :write_code] <-
         {:authorization, Sandboxes.get_team_authorization(sandbox, team)},
       pitcher_authorization <- translate_authorization(authorization, scopes),
       {:ok, sandbox, response} <-
         Operations.start_vm(
           sandbox,
           team,
           %{
             authorization: pitcher_authorization,
             authorization_method: :team,
             tier: params[:tier],
             hibernation_timeout_seconds: params[:hibernation_timeout_seconds],
             cloudflare_ipcountry: params[:ipcountry] || get_req_header(conn, "cf-ipcountry"),
             forked_from_sandbox: get_forked_from_sandbox(sandbox),
             pitcher_manager_url: get_req_header(conn, "x-pitcher-manager-url") |> List.first(),
             automatic_wakeup_config: params[:automatic_wakeup_config],
             feature_flags: params[:feature_flags] || %{}
           }
         ) do
    conn
    |> put_view(VMController.StartResponse)
    |> put_status(200)
    |> render(:json, pitcher_response: response, id: sandbox.shortid)
  else
    # ... error handling
  end
end

File: lib/code_sandbox/vm/operations.ex

Update start_vm function to handle feature flags:

@type start_vm_opts :: %{
        required(:authorization) => String.t(),
        required(:authorization_method) => :team,
        optional(:automatic_wakeup_config) => %{http: boolean(), websocket: boolean()} | nil,
        optional(:cloudflare_ipcountry) => String.t(),
        optional(:ip) => String.t(),
        optional(:forked_from_sandbox) => Sandbox.t() | nil,
        optional(:hibernation_timeout_seconds) => pos_integer(),
        optional(:is_new_sandbox) => boolean(),
        optional(:pitcher_manager_url) => String.t() | nil,
        optional(:tier) => String.t(),
        optional(:feature_flags) => map()
      }

def start_vm(sandbox, team, start_vm_opts) do
  with {:ok, %Resources{} = resources} <- get_vm_resources(sandbox, team, start_vm_opts[:tier]),
       {:ok, resources} <-
         validate_hibernation_timeout(resources, start_vm_opts[:hibernation_timeout_seconds]),
       {:ok, updated_sandbox} <- maybe_update_sandbox_type(sandbox, start_vm_opts[:feature_flags]),
       {:ok, response} <-
         External.Pitcher.start_sandbox_instance(
           nil,
           updated_sandbox,
           start_vm_opts.authorization,
           authorization_method: start_vm_opts.authorization_method,
           resources: resources,
           ip: start_vm_opts[:ip],
           cloudflare_ipcountry: start_vm_opts[:cloudflare_ipcountry],
           forked_from_sandbox: start_vm_opts[:forked_from_sandbox],
           pitcher_manager_url: start_vm_opts[:pitcher_manager_url],
           is_new_sandbox: start_vm_opts[:is_new_sandbox],
           automatic_wakeup_config: start_vm_opts[:automatic_wakeup_config],
           feature_flags: start_vm_opts[:feature_flags]
         ) do
    {:ok, updated_sandbox, response}
  end
end

defp maybe_update_sandbox_type(sandbox, feature_flags) do
  case get_in(feature_flags, ["sandboxType"]) do
    "pint" -> 
      sandbox
      |> Ecto.Changeset.change(sandbox_type: "pint")
      |> Repo.update()
    _ -> 
      {:ok, sandbox}
  end
end

File: lib/external/pitcher.ex

Update start_sandbox_instance to pass feature flags:

def start_sandbox_instance(user, sandbox, authorization, opts \\ []) do
  # ... existing code for building body
  
  feature_flags = Keyword.get(opts, :feature_flags, %{})
  
  body =
    %{
      # ... existing body fields
      featureFlags: feature_flags,
      annotations: %{
        # ... existing annotations
        "sandboxType" => sandbox.sandbox_type || "standard",
        "pintEnabled" => to_string(get_in(feature_flags, ["sandboxType"]) == "pint")
      }
    }
    # ... rest of body building

  # ... rest of function
end

3. API Schema Updates

OpenAPI Specification Updates

Add feature flags to the VM start request schema:

components:
  schemas:
    VMStartRequest:
      type: object
      properties:
        # ... existing properties
        feature_flags:
          type: object
          properties:
            sandboxType:
              type: string
              enum: ["standard", "pint"]
              description: "The sandbox type to create"
          additionalProperties: true
          description: "Feature flags for experimental functionality"

4. Database Migration

Create migration file:

defmodule CodeSandbox.Repo.Migrations.AddSandboxTypeToSandboxes do
  use Ecto.Migration

  def change do
    alter table(:sandboxes) do
      add :sandbox_type, :string, default: "standard"
    end

    create index(:sandboxes, [:sandbox_type])
  end
end

Testing Strategy

Unit Tests

  1. SDK Tests

    • Test feature flag passing in sandbox creation
    • Test backward compatibility with existing API
    • Test validation of feature flag values
  2. Server Tests

    • Test VM controller handles feature flags correctly
    • Test sandbox type sto rage in database
    • Test pitcher integration with feature flags
    • Test authorization remains intact
  3. Integration Tests

    • End-to-end sandbox creation with pint feature flag
    • Test feature flag propagation through entire stack
    • Test VM startup with pint configuration

Test Cases

# Example test case
test "creates sandbox with pint feature flag", %{conn: conn, team: team} do
  sandbox = insert(:sandbox, team: team, v2: true)
  
  params = %{
    "feature_flags" => %{
      "sandboxType" => "pint"
    }
  }
  
  conn = post(conn, "/api/v1/vm/#{sandbox.shortid}/start", params)
  
  assert %{"id" => _} = json_response(conn, 200)
  
  updated_sandbox = Repo.get!(Sandbox, sandbox.id)
  assert updated_sandbox.sandbox_type == "pint"
end

Load Testing

  • Test performance impact of feature flag processing
  • Ensure no degradation in sandbox creation latency
  • Test concurrent sandbox creation with mixed feature flags

Dependencies and Considerations

Dependencies

  • Elixir/Phoenix: Server-side feature flag handling
  • TypeScript: SDK type definitions and client-side logic
  • PostgreSQL: Database schema changes
  • Pitcher Manager: Must support receiving and processing feature flags

Backward Compatibility

  • All existing sandbox creation flows must continue working
  • Default sandbox type remains "standard"
  • Feature flag parameter is optional
  • No breaking changes to existing APIs

Feature Flag Management

  • Consider using a feature flag service (LaunchDarkly, etc.) for runtime control
  • Implement gradual rollout capabilities
  • Add monitoring and metrics for pint sandbox usage

Monitoring and Observability

  • Add metrics for pint sandbox creation rates
  • Monitor feature flag usage patterns
  • Track error rates for pint vs standard sandboxes
  • Add logging for feature flag propagation

Security Considerations

  • Validate feature flag inputs to prevent injection attacks
  • Ensure feature flags don't expose sensitive information
  • Maintain proper authorization checks regardless of sandbox type
  • Audit feature flag changes for compliance

Performance Considerations

  • Feature flag processing should be minimal overhead
  • Database queries should use indexes for sandbox_type
  • Consider caching feature flag configurations
  • Monitor resource usage of pint vs standard sandboxes

Rollout Plan

Phase 1: Infrastructure

  • Implement database schema changes
  • Add feature flag support to SDK and server
  • Deploy with feature disabled by default

Phase 2: Limited Testing

  • Enable for internal testing teams
  • Validate feature flag propagation
  • Monitor for issues and performance impacts

Phase 3: Gradual Rollout

  • Enable for select beta users
  • Monitor usage patterns and feedback
  • Scale based on results

Phase 4: General Availability

  • Enable feature for all eligible users
  • Document in API documentation
  • Provide migration guides if needed

Success Criteria

  • Feature flag successfully propagates from SDK to pitcher-manager
  • Pint sandbox type can be created and started via public APIs
  • No performance degradation for existing sandbox types
  • Feature can be enabled/disabled without code deployments
  • All existing functionality remains intact
  • Comprehensive test coverage (>90%) for new functionality

Risk Mitigation

  • Risk: Breaking existing flows Mitigation: Extensive backward compatibility testing and gradual rollout

  • Risk: Performance degradation Mitigation: Load testing and performance monitoring

  • Risk: Feature flag propagation failures Mitigation: Comprehensive integration testing and error handling

  • Risk: Security vulnerabilities Mitigation: Security review of feature flag handling and validation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment