{"record":{"id":"b7571786d1a2934c","repo":"twentyhq/twenty","slug":"missing-name-env-var","errorCode":null,"errorMessage":"Missing ${name} env var","messagePattern":"Missing (.+?) env var","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/twenty-apps/internal/twenty-partners/src/scripts/purge-soft-deleted.ts","lineNumber":21,"sourceCode":"// Twenty SOFT-deletes (sets deletedAt); the row stays in the DB and keeps holding\n// unique constraints (e.g. company domain, partner slug). But normal queries —\n// including the import's existence checks — exclude soft-deleted rows. So after a\n// UI \"delete\" or a partial import that got rolled back, re-running the import hits\n// \"A duplicate entry was detected\" on records it cannot see. This purges those\n// ghosts permanently so idempotent upserts work again.\n//\n// Only touches soft-deleted rows (deletedAt IS NOT NULL); active/default data is\n// left untouched. One bulk destroy per object, so it is not rate-limited.\n//\n//   yarn purge            # against .env.local\n//   yarn purge:prod       # against .env.prod\n//\nimport { config } from 'dotenv';\nconfig({ path: process.env.ENV_FILE ?? '.env.local' });\n\nconst requireEnv = (name: string): string => {\n  const value = process.env[name];\n  if (!value) throw new Error(`Missing ${name} env var`);\n  return value;\n};\n\n// Objects the import writes to. partners + partnerContents are app custom objects;\n// companies + opportunities are standard but populated by the import.\nconst OBJECTS = ['companies', 'partners', 'opportunities', 'partnerContents'] as const;\n\nconst gql = async (url: string, key: string, query: string): Promise<any> => {\n  const response = await fetch(`${url.replace(/\\/$/, '')}/graphql`, {\n    method: 'POST',\n    headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },\n    body: JSON.stringify({ query }),\n  });\n  const json: any = await response.json();\n  if (json.errors?.length) throw new Error(JSON.stringify(json.errors));\n  return json.data;\n};\n","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/twentyhq/twenty/blob/1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6/packages/twenty-apps/internal/twenty-partners/src/scripts/purge-soft-deleted.ts#L3-L39","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst requireEnv = (name: string): string => {\n  const value = process.env[name];\n  if (!value) throw new Error(`Missing ${name} env var`);\n  return value;\n};\n\n// after — name the missing variable and the file that was loaded\nconst requireEnv = (name: string): string => {\n  const value = process.env[name];\n  if (!value) {\n    throw new Error(\n      `Missing ${name} env var (loaded from ${process.env.ENV_FILE ?? '.env.local'})`,\n    );\n  }\n  return value;\n};","handlingStrategy":"validation","validationCode":"const requireEnv = (name: string): string => {\n  const value = process.env[name];\n  if (!value) {\n    throw new Error(\n      `Missing ${name} env var (loaded ${process.env.ENV_FILE ?? '.env.local'})`,\n    );\n  }\n  return value;\n};\n\n// Fail fast at startup with all missing keys, not one at a time:\nconst REQUIRED = ['PARTNERS_API_URL', 'PARTNERS_API_KEY'] as const;\nconst missing = REQUIRED.filter((k) => !process.env[k]);\nif (missing.length) {\n  throw new Error(`Missing required env vars: ${missing.join(', ')}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["env","configuration","script","purge","partners"],"backgroundTag":null,"analyzedSha":"1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6","analyzedAt":"2026-08-12T15:37:27.593Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}