env-validationlisted
Install: claude install-skill voidcorp-core/void-harness
# env-validation
Use when adding any new environment variable, or when working in a project that doesn't yet have `@repo/core/env`. Env vars are an invisible trust boundary — they're inputs from outside the process, and "the database URL is undefined" should explode at boot, not at the first query.
This skill is the void-harness operational form. Composes with `harness:security-guidance` (env doctrine) and enforced by the `no-process-env-in-app` hook.
## The principle
```
process.env.X → used ONLY inside @repo/core/env
import { env } from '@repo/core' → used everywhere else
```
`@repo/core/env` parses + validates `process.env` ONCE, exposes a typed object. Any business code reading `process.env` directly is forbidden (enforced by the `no-process-env-in-app` hook).
## The schema
```ts
// packages/core/src/env.ts
import { z } from 'zod';
const ServerSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
DATABASE_URL: z.string().url(),
STRIPE_SECRET_KEY: z.string().regex(/^sk_(test|live)_/),
STRIPE_WEBHOOK_SECRET: z.string().min(10),
SENTRY_DSN: z.string().url().optional(),
OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url().optional(),
});
const ClientSchema = z.object({
NEXT_PUBLIC_APP_URL: z.string().url(),
NEXT_PUBLIC_SENTRY_DSN: z.string().url().optional(),
});
function parseEnv() {
const isServer = typeof window === 'undefined';
const server = isServer ? ServerSchema.parse(process.env) : ({} as z.infer<typeof Server