# How OpenClaude Loads Provider Environment Files at Startup

> Learn how OpenClaude loads provider environment files at startup. Discover the process of reading .env files, sanitizing variables, and caching configurations for your CLI session.

- Repository: [Gitlawb/openclaude](https://github.com/Gitlawb/openclaude)
- Tags: internals
- Published: 2026-09-02

---

**OpenClaude loads provider environment files by first reading the global `.env` with `dotenv`, then extracting and sanitizing provider-specific variables in [`providerConfig.ts`](https://github.com/Gitlawb/openclaude/blob/main/providerConfig.ts), and caching the result for the CLI session.**

The **provider configuration** system in OpenClaude controls how each model integration receives its credentials, endpoints, and settings. According to the Gitlawb/openclaude source code, this configuration is constructed from environment files through a three-stage pipeline that ensures security and consistency.

## Step 1: Load the Global `.env` File

The bootstrap process begins in [`src/bootstrap/state.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/bootstrap/state.ts), where the CLI calls the `dotenv` package to populate `process.env`.

```ts
// src/bootstrap/state.ts
import { config as loadDotEnv } from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectRoot = join(__dirname, '..', '..');

loadDotEnv({ path: `${projectRoot}/.env` });

```

This single call reads the repository-root `.env` file and injects all key-value pairs into the Node.js environment. The path resolution ensures the file is loaded relative to the project structure, not the current working directory.

## Step 2: Merge Provider-Specific Variables

After the global file loads, the **provider-configuration module** at [`src/services/api/providerConfig.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/providerConfig.ts) scans `process.env` for variables matching each provider's naming convention.

```ts
// src/services/api/providerConfig.ts
export function resolveProviderRequest(
  provider: ProviderId,
  env: Record<string, string | undefined> = process.env,
): ResolvedProviderRequest {
  const vars = pickProviderEnvVars(provider, env);
  // …construct a ResolvedProviderRequest from `vars`
}

```

The helper `pickProviderEnvVars` performs two critical sanitization tasks:

- **Filters placeholder sentinels** — Removes entries containing `${` that may leak from dotenv templates or Windows-style variable expansions
- **Normalizes malformed values** — Strips literal `${VAR}` strings that can appear in certain shell environments

Supported provider variables follow predictable patterns: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `CLAUDE_CODE_GEMINI_PROJECT_ID`, and similar prefixed keys.

## Step 3: Cache the Resolved Configuration

To prevent redundant environment reads and guarantee consistency, [`src/bootstrap/state.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/bootstrap/state.ts) implements a singleton-style cache:

```ts
// src/bootstrap/state.ts (excerpt)
let cachedProviderRequest: ResolvedProviderRequest | undefined;

export function getProviderRequest(provider: ProviderId) {
  if (!cachedProviderRequest) {
    cachedProviderRequest = resolveProviderRequest(provider);
  }
  return cachedProviderRequest;
}

```

The first call to `getProviderRequest` triggers resolution; subsequent calls return the identical cached object. This ensures every component receives the same fully-resolved configuration throughout the CLI run.

## Key Files in the Loading Pipeline

| File | Responsibility |
|------|--------------|
| [`src/bootstrap/state.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/bootstrap/state.ts) | Entry point; loads global `.env` via `dotenv.config()` and caches resolved requests |
| [`src/services/api/providerConfig.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/providerConfig.ts) | Parses provider-specific variables, removes template sentinels, builds `ResolvedProviderRequest` |
| [`src/utils/providerProfiles.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/providerProfiles.ts) | Utilities for cleaning template/dotenv sentinels post-load |
| [`src/utils/providerFlag.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/providerFlag.ts) | Documents sentinel filtering logic for endpoints |

## Security Considerations in Provider Env Loading

The OpenClaude provider environment loading system implements **defensive filtering** at multiple layers:

- **Sentinel detection** — Any value containing `${` is treated as a template placeholder and discarded
- **Strict key matching** — Only variables matching known provider prefixes are extracted
- **Single-read caching** — Environment is never re-parsed after initial load, preventing mid-session tampering

These safeguards prevent accidental credential leakage from incomplete `.env` configurations or shell environment pollution.

## Summary

- **Global loading** — [`src/bootstrap/state.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/bootstrap/state.ts) calls `dotenv.config()` to read the repository-root `.env` file
- **Provider extraction** — [`src/services/api/providerConfig.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/providerConfig.ts) filters `process.env` for provider-specific keys and strips template sentinels
- **Request caching** — Resolved configurations are cached in bootstrap state to ensure consistency across the CLI session
- **Sentinel removal** — Placeholder patterns like `${VAR}` are automatically discarded to prevent credential leaks

## Frequently Asked Questions

### Where does OpenClaude look for the `.env` file?

OpenClaude resolves the `.env` path relative to [`src/bootstrap/state.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/bootstrap/state.ts)'s location, traversing up two directory levels to reach the repository root. This ensures the file is found regardless of the working directory from which the CLI is invoked.

### What happens if a provider environment variable contains a template placeholder?

The `pickProviderEnvVars` helper in [`src/services/api/providerConfig.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/providerConfig.ts) detects values containing `${` and deletes them from the configuration. This prevents accidental use of unexpanded shell variables or copy-pasted template strings as actual credentials.

### Why does OpenClaude cache the provider configuration?

Caching in `getProviderRequest()` guarantees that all components reference identical credential sets throughout a CLI session. Without caching, environment mutations or filesystem changes could cause inconsistent behavior between different model integrations.

### Which provider environment variable prefixes are supported?

As implemented in [`src/services/api/providerConfig.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/api/providerConfig.ts), OpenClaude recognizes provider-prefixed keys including `OPENAI_*`, `ANTHROPIC_*`, and `CLAUDE_CODE_GEMINI_*`. The system uses dynamic key construction based on the `ProviderId` enum to match variables to their respective integrations.