How Freebuff Handles Environment Variable Validation Across Test Packages

Freebuff uses a centralized Zod-based schema in common/src/env-schema.ts to validate environment variables, with test packages importing a validated env object from common/src/env.ts to ensure consistent type-safe access across the entire codebase.

Freebuff is an open-source AI coding assistant that enforces strict environment variable validation to prevent runtime misconfigurations. The project uses a shared validation layer that both production code and test suites rely on, eliminating drift between test mocks and real deployment settings.

The Centralized Validation Architecture

According to the CodebuffAI/freebuff source code, all environment variable handling flows through two core files in the common package.

Schema Definition in common/src/env-schema.ts

The clientEnvSchema uses Zod to declare every public variable (NEXT_PUBLIC_*) that client-side code may access:

// common/src/env-schema.ts
import { z } from "zod";

export const clientEnvSchema = z.object({
  NEXT_PUBLIC_CB_ENVIRONMENT: z.string().optional(),
  NEXT_PUBLIC_CB_API_URL: z.string().url().optional(),
  // ... additional public variables
});

export type ClientEnv = z.infer<typeof clientEnvSchema>;

// Server-only variables extend the client schema
export const serverEnvSchema = clientEnvSchema.extend({
  OPEN_ROUTER_API_KEY: z.string().min(1),
  DATABASE_URL: z.string().startsWith("postgres://"),
});

export type ServerEnv = z.infer<typeof serverEnvSchema>;

The clientProcessEnv helper filters process.env to only keys matching the schema's declared variables, preventing accidental exposure of undeclared secrets.

Runtime Parsing in common/src/env.ts

The env.ts module executes validation immediately on import, failing fast on any configuration errors:

// common/src/env.ts
import { clientEnvSchema, clientProcessEnv } from "./env-schema";

const parsedEnv = clientEnvSchema.safeParse(clientProcessEnv);

if (!parsedEnv.success) {
  throw new Error(
    `Invalid client environment variables: ${JSON.stringify(parsedEnv.error.format())}`
  );
}

export const env = parsedEnv.data;

This fail-fast pattern ensures that invalid environments never reach application logic. Tests can catch this behavior by asserting on the thrown error.

How Test Packages Consume the Validation Layer

Freebuff's test packages do not reimplement validation logic. Instead, they import the same env object used in production, guaranteeing identical behavior.

SDK Test Package Validation

The SDK tests verify that environment-file access policies are enforced regardless of test environment overrides:

// sdk/src/__tests__/read-files.test.ts
import { env } from "../../common/src/env";

test("blocks access to .env files regardless of process.env", () => {
  // Even with this override, the env policy blocks file reads
  process.env.SOME_SECRET_KEY = "exposed";
  
  // env.OPEN_ROUTER_API_KEY remains typed and validated
  expect(() => accessEnvFile(".env.local")).toThrow(PermissionError);
});

The run-file-filter.test.ts file similarly confirms that environment validation executes before any file-filter callbacks run, preventing secret leakage through misconfigured filters.

Agent Test Package Validation

Agent tests validate that AI agents respect typed environment variables during execution:

// agents/__tests__/base2.test.ts
import { env } from "../../common/src/env";

test("agent uses API key only when properly configured", () => {
  // TypeScript enforces env.OPEN_ROUTER_API_KEY exists and is string
  const agent = new Agent({
    apiKey: env.OPEN_ROUTER_API_KEY,
  });
  
  expect(agent.isConfigured).toBe(!!env.OPEN_ROUTER_API_KEY);
});

The base3.test.ts suite extends this to verify graceful degradation when optional variables are absent.

Testing Validation Failures Directly

Freebuff includes explicit tests for the validation mechanism itself, ensuring schema changes maintain expected error behavior:

// sdk/src/__tests__/env-validation.test.ts
import { clientEnvSchema, clientProcessEnv } from "../../common/src/env-schema";

test("safeParse returns error for type mismatches", () => {
  const invalidEnv = {
    ...clientProcessEnv,
    NEXT_PUBLIC_CB_ENVIRONMENT: 123, // wrong type
  };
  
  const result = clientEnvSchema.safeParse(invalidEnv);
  expect(result.success).toBe(false);
  expect(result.error!.issues[0].path).toContain("NEXT_PUBLIC_CB_ENVIRONMENT");
});

test("throws on unexpected environment shape", () => {
  // Simulate fresh import with bad environment
  jest.isolateModules(() => {
    process.env = { INVALID_VAR: "value" };
    expect(() => require("../../common/src/env")).toThrow(/Invalid client environment/);
  });
});

Propagation of Schema Changes

Because all packages import from the same common/src/env.ts module, modifying the schema immediately affects every test suite:

Change Type Impact Across Test Packages
Add required variable All tests fail until process.env updated; single fix propagates
Make variable optional Tests gain flexibility; no code changes required
Type narrowing (e.g., url() validation) Invalid test values caught immediately at parse time
Server-only addition Client tests unaffected; server tests gain new typed field

This single source of truth prevents the common bug pattern where tests pass with mocked values that would fail in production.

Summary

  • Freebuff validates environment variables using Zod schemas defined in common/src/env-schema.ts with separate client and server variants
  • common/src/env.ts parses process.env on import, throwing immediately for invalid configurations
  • Test packages import the validated env object directly, ensuring test behavior matches production
  • SDK tests verify file-access policies respect env validation before secret leakage can occur
  • Agent tests confirm AI components use typed environment variables with proper optional handling
  • Schema changes propagate automatically to all test suites through the shared module dependency

Frequently Asked Questions

Does Freebuff allow different environment variables in tests versus production?

No. All test packages import from common/src/env.ts, which uses the identical schema validation as production. Tests can override process.env values before importing, but the variable names and types must match the schema exactly.

How does Freebuff prevent secrets from appearing in test logs?

The clientEnvSchema explicitly excludes server-only variables. Tests importing env receive only the filtered ClientEnv type. Server tests use a separate serverEnvSchema with additional secrets, but both schemas apply the same runtime validation pattern.

What happens when a required environment variable is missing in CI?

The safeParse check in env.ts throws an error with a formatted Zod error message describing which variables are missing or invalid. This causes immediate test or application failure, alerting developers to configuration gaps before deployment.

Can tests validate custom environment shapes without modifying the shared schema?

Tests can import clientEnvSchema and use Zod's .extend() or .pick() methods to create temporary validation schemas for specific test scenarios. However, the production env export always uses the canonical schema to prevent divergence.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →