twentyhq/twenty · error · Error
Missing ${name} env var
Error message
Missing ${name} env var What it means
The `purge-soft-deleted` maintenance script defines a `requireEnv` helper that reads a variable from `process.env` (loaded from `.env.local` or the file in `ENV_FILE`) and throws this generic message when one is unset. The script needs several configured values (Twenty GraphQL endpoint and an API key) to issue bulk-destroy operations against soft-deleted rows. Any missing one aborts before any destructive call is made.
Source
Thrown at packages/twenty-apps/internal/twenty-partners/src/scripts/purge-soft-deleted.ts:21
// Twenty SOFT-deletes (sets deletedAt); the row stays in the DB and keeps holding
// unique constraints (e.g. company domain, partner slug). But normal queries —
// including the import's existence checks — exclude soft-deleted rows. So after a
// UI "delete" or a partial import that got rolled back, re-running the import hits
// "A duplicate entry was detected" on records it cannot see. This purges those
// ghosts permanently so idempotent upserts work again.
//
// Only touches soft-deleted rows (deletedAt IS NOT NULL); active/default data is
// left untouched. One bulk destroy per object, so it is not rate-limited.
//
// yarn purge # against .env.local
// yarn purge:prod # against .env.prod
//
import { config } from 'dotenv';
config({ path: process.env.ENV_FILE ?? '.env.local' });
const requireEnv = (name: string): string => {
const value = process.env[name];
if (!value) throw new Error(`Missing ${name} env var`);
return value;
};
// Objects the import writes to. partners + partnerContents are app custom objects;
// companies + opportunities are standard but populated by the import.
const OBJECTS = ['companies', 'partners', 'opportunities', 'partnerContents'] as const;
const gql = async (url: string, key: string, query: string): Promise<any> => {
const response = await fetch(`${url.replace(/\/$/, '')}/graphql`, {
method: 'POST',
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ query }),
});
const json: any = await response.json();
if (json.errors?.length) throw new Error(JSON.stringify(json.errors));
return json.data;
};
View on GitHub (pinned to 1f5dd2bbd2)
Solutions
- Open the `.env.local` (or `.env.prod` / the file in `ENV_FILE`) and confirm every variable the script reads via `requireEnv` is present and non-empty.
- Run with `ENV_FILE` explicitly set to the file you intend, e.g. `ENV_FILE=.env.prod yarn purge:prod`, to rule out a path mismatch.
- Check the script for the list of `requireEnv('...')` call sites and ensure each name matches your env file exactly (case-sensitive).
- If a key is genuinely optional in your setup, refactor `requireEnv` to a `getEnv` variant with a default rather than leaving it unset.
Example fix
// before
const requireEnv = (name: string): string => {
const value = process.env[name];
if (!value) throw new Error(`Missing ${name} env var`);
return value;
};
// after — name the missing variable and the file that was loaded
const requireEnv = (name: string): string => {
const value = process.env[name];
if (!value) {
throw new Error(
`Missing ${name} env var (loaded from ${process.env.ENV_FILE ?? '.env.local'})`,
);
}
return value;
}; Defensive patterns
Strategy: validation
Validate before calling
const requireEnv = (name: string): string => {
const value = process.env[name];
if (!value) {
throw new Error(
`Missing ${name} env var (loaded ${process.env.ENV_FILE ?? '.env.local'})`,
);
}
return value;
};
// Fail fast at startup with all missing keys, not one at a time:
const REQUIRED = ['PARTNERS_API_URL', 'PARTNERS_API_KEY'] as const;
const missing = REQUIRED.filter((k) => !process.env[k]);
if (missing.length) {
throw new Error(`Missing required env vars: ${missing.join(', ')}`);
} Prevention
- Keep `.env.example` in sync with every `requireEnv` call site so missing keys are obvious.
- Validate all required vars at script entry, collecting missing names, before any destructive work.
- Always pass `ENV_FILE` explicitly when targeting prod so the default `.env.local` is never used by accident.
- Never commit populated env files; use a secret manager for prod values.
When it happens
Trigger: Running `yarn purge` / `yarn purge:prod` when one of the required env vars (e.g. the partners API base URL or the bearer API key) is absent from `.env.local` / `.env.prod`, or when `ENV_FILE` points at a file that does not exist or is missing keys.
Common situations: A freshly cloned repo without a populated `.env.local`, a typo in a variable name, pointing `ENV_FILE` at the wrong file, or copying a `.env.prod` that omits a key the script added in a newer revision.
Related errors
- Missing ${name} env var
- Server error (${response.status})
- Missing env var: ${name}
- upsertRowLevelPermissionPredicates returned fewer than 2 pre
- upsertRowLevelPermissionPredicates returned no predicate for
AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12).
Data as JSON: /api/errors/b7571786d1a2934c.
Report an issue: GitHub.