Scattered process.env.* calls cause typos that crash production and make it easy to miss required configuration. Claude Code can design a type-safe environment configuration module that validates on startup.
CLAUDE.md for Environment Variable Management
## Environment Variable Rules
### Access pattern
- Never use process.env.* directly in code
- All env vars accessed through src/config/env.ts only
- Validate at startup — missing required vars crash with clear message
### Type safety
- Zod schema defines all environment variables
- Auto-coerce: string → number/boolean/URL
- Explicit default values only
### Secret management
- Sensitive values (API keys, DB passwords) in .env (NEVER committed)
- Keep .env.example up to date (keys only, no real values)
- CI/CD secrets: use platform's secret management
### Environments
- development: .env.local
- test: .env.test
- production: real environment variables (no .env files)
### Prohibited
- Committing .env files (add to .gitignore)
- process.env branching (other than NODE_ENV === 'production')
- Using real secrets as default values
Generating the Type-Safe Env Module
Generate a type-safe environment variables module using Zod.
Variables:
- DATABASE_URL: string (valid PostgreSQL URL)
- REDIS_URL: string (valid Redis URL)
- JWT_SECRET: string (minimum 32 characters)
- PORT: number (default: 3000)
- NODE_ENV: 'development' | 'test' | 'production'
- LOG_LEVEL: 'debug' | 'info' | 'warn' | 'error' (default: 'info')
- SENDGRID_API_KEY: string (optional — only needed if email enabled)
- CORS_ORIGINS: string[] (comma-separated URL list)
Requirements:
- Validate at startup — show ALL invalid variables at once, then crash
- Auto-coerce PORT from string to number
- Export TypeScript types
Save to: src/config/env.ts
Generated Env Module
// src/config/env.ts
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url('DATABASE_URL must be a valid URL'),
REDIS_URL: z.string().url('REDIS_URL must be a valid URL'),
JWT_SECRET: z
.string()
.min(32, 'JWT_SECRET must be at least 32 characters'),
PORT: z.coerce.number().int().min(1).max(65535).default(3000),
NODE_ENV: z
.enum(['development', 'test', 'production'])
.default('development'),
LOG_LEVEL: z
.enum(['debug', 'info', 'warn', 'error'])
.default('info'),
SENDGRID_API_KEY: z.string().optional(),
CORS_ORIGINS: z
.string()
.transform((val) => val.split(',').map((s) => s.trim()))
.default('http://localhost:3000'),
});
const result = envSchema.safeParse(process.env);
if (!result.success) {
const errors = result.error.issues.map(
(issue) => ` - ${issue.path.join('.')}: ${issue.message}`
);
console.error('Environment validation failed:');
console.error(errors.join('\n'));
process.exit(1);
}
export const env = result.data;
export type Env = typeof env;
Startup output when misconfigured:
Environment validation failed:
- DATABASE_URL: Invalid url
- JWT_SECRET: String must contain at least 32 character(s)
Generating .env.example
Read src/config/env.ts and generate .env.example.
Requirements:
- List all environment variables
- Add descriptive comments
- Use example values (never real secrets)
- Show default values where applicable
Generated:
# Database
# PostgreSQL connection string
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
# Redis
REDIS_URL=redis://localhost:6379
# Security
# Minimum 32 characters — generate with: openssl rand -base64 32
JWT_SECRET=change-me-to-a-random-32-char-secret
# Server
PORT=3000
NODE_ENV=development
# Logging (debug|info|warn|error)
LOG_LEVEL=info
# Email (optional — leave empty to disable)
# SENDGRID_API_KEY=SG.your-key-here
# CORS — comma-separated allowed origins
CORS_ORIGINS=http://localhost:3000,http://localhost:3001
Hook: Detect Direct process.env Access
# .claude/hooks/check_env_usage.py
import json, re, sys
data = json.load(sys.stdin)
content = data.get("tool_input", {}).get("content", "") or ""
fp = data.get("tool_input", {}).get("file_path", "")
# Skip env module itself and test files
if not fp or any(x in fp for x in ["env.ts", "test", "spec", ".env"]):
sys.exit(0)
if not fp.endswith((".ts", ".js")):
sys.exit(0)
if re.search(r'process\.env\.\w+', content):
print("[ENV] Direct process.env access detected.", file=sys.stderr)
print("[ENV] Use src/config/env.ts instead.", file=sys.stderr)
sys.exit(2) # Block
sys.exit(0)
Summary
Design environment variable management with Claude Code:
- CLAUDE.md — Single-source access through env module, validation required
- Zod schema — Type-safe definitions with coercion and validation
- Startup validation — Show ALL missing vars at once, then crash cleanly
- .env.example — Keep it in sync, never commit real values
- Hooks — Block direct process.env access automatically
Security Pack (¥1,480) includes /security-check for secret management review — exposed secrets, missing validation, direct env access.
Myouga (@myougatheaxo) — Claude Code engineer focused on production configuration.
Top comments (0)