Environment Variable Hygiene
Best practices for managing environment variables across deployment platforms.
Triggers
Invoke when user mentions:
- "env var", "environment variable", "production deploy"
- "webhook secret", "API key", "token"
- "Invalid character in header", "ERR_INVALID_CHAR"
- "silent failure", "webhook not working"
- "Vercel", "Convex", "deployment", "secrets"
Core Principles
1. Trailing Whitespace Kills
Env vars with \n or trailing spaces cause cryptic errors:
- "Invalid character in header content" (HTTP headers)
- Webhook signature mismatch
- Silent authentication failures
Root Cause: Copy-paste or echo introduces invisible characters.
Prevention:
# ✅ Use printf, not echo
printf '%s' 'sk_live_xxx' | vercel env add STRIPE_SECRET_KEY production
# ✅ Trim when setting
bunx convex env set --prod KEY "$(echo 'value' | tr -d '\n')" # or: npx convex ...
# ❌ Don't use echo directly
echo "sk_live_xxx" | vercel env add KEY production # May add \n
2. Cross-Platform Parity
Shared tokens (webhook secrets, auth tokens) must be identical across platforms:
- Vercel ↔ Convex
- Frontend ↔ Backend
- Dev ↔ Staging ↔ Prod (within each platform)
Common Pitfall: Set token on one platform, forget the other.
Prevention:
# Generate token once
TOKEN=$(openssl rand -hex 32)
# Set on ALL platforms
bunx convex env set --prod CONVEX_WEBHOOK_TOKEN "$(printf '%s' "$TOKEN")" # or: npx convex ...
printf '%s' "$TOKEN" | vercel env add CONVEX_WEBHOOK_TOKEN production
3. Validate Format Before Use
API keys have specific formats. Validate before deployment:
| Service | Pattern | Example |
|---------|---------|---------|
| Stripe Secret | sk_(test\|live)_[A-Za-z0-9]+ | sk_live_xxx |
| Stripe Public | pk_(test\|live)_[A-Za-z0-9]+ | pk_live_xxx |
| Stripe Webhook | whsec_[A-Za-z0-9]+ | whsec_xxx |
| Stripe Price | price_[A-Za-z0-9]+ | price_xxx |
| Clerk Secret | sk_(test\|live)_[A-Za-z0-9]+ | sk_live_xxx |
4. Dev ≠ Prod
Separate deployments have separate env var stores:
- Setting
.env.localdoesn't affect production - Convex dev and prod are separate deployments
- Vercel has per-environment variables
Always verify prod separately:
# Convex
bunx convex env list --prod # or: npx convex ...
# Vercel
vercel env ls --environment=production
5. CLI Environment Gotcha
CONVEX_DEPLOYMENT=prod:xxx npx convex data may return dev data.
Always use explicit flags:
# ❌ Unreliable
CONVEX_DEPLOYMENT=prod:xxx npx convex data
# ✅ Reliable
bunx convex run --prod module:function # or: npx convex ...
bunx convex env list --prod
6. Env Load Semantics (Restart Required)
Many runtimes load .env.local once at process start.
If code logs "missing X" but .env.local has X:
- you edited
.env.localafter server started. Restart dev server. - your shell env overrides dotenv (incl empty string):
printenv KEYthenunset KEY.
Quick Reference
Setting Env Vars Safely
Convex:
# Dev
bunx convex env set KEY "value" # or: npx convex ...
# Prod (use --prod flag)
bunx convex env set --prod KEY "$(printf '%s' 'value')"
Vercel:
# Production
printf '%s' 'value' | vercel env add KEY production
# With explicit environment
vercel env add KEY production --force
Checking Env Vars
Convex:
bunx convex env list # dev
bunx convex env list --prod # prod
Vercel:
vercel env ls # all
vercel env ls --environment=production # prod only
Detecting Issues
Trailing whitespace:
# Check Convex prod
bunx convex env list --prod | while IFS= read -r line; do
if [[ "$line" =~ [[:space:]]$ ]]; then
echo "WARNING: $(echo "$line" | cut -d= -f1) has trailing whitespace"
fi
done
Format validation:
# Validate Stripe key format
value=$(bunx convex env list --prod | grep "^STRIPE_SECRET_KEY=" | cut -d= -f2-)
[[ "$value" =~ ^sk_(test|live)_[A-Za-z0-9]+$ ]] || echo "Invalid format"
References
See references/ directory:
format-patterns.md- Regex patterns for common servicesplatform-specifics.md- Vercel, Convex, Railway platform detailshygiene-checklist.md- Pre-deployment validation checklistparity-verification.md- Cross-platform token verification
Related Commands
/pre-deploy- Comprehensive pre-deployment checklist/env-parity-check- Cross-platform token verification/stripe-check- Stripe-specific environment audit
Based on 2026-01-17 incident: Trailing \n in STRIPE_SECRET_KEY caused "Invalid character in header" error. Token mismatch between Vercel and Convex caused silent webhook failures.