Skip to content

fix(api): treat blank optional env values as unset - #185

Open
mianawais78 wants to merge 1 commit into
theam:mainfrom
mianawais78:fix/blank-optional-env-values
Open

fix(api): treat blank optional env values as unset#185
mianawais78 wants to merge 1 commit into
theam:mainfrom
mianawais78:fix/blank-optional-env-values

Conversation

@mianawais78

Copy link
Copy Markdown

Summary

Fixes the API crashing at startup after copying .env.example to .env, which is the documented first step of the self-host quickstart.

.env.example ships VERCEL_TOKEN, VERCEL_OIDC_TOKEN, VERCEL_TEAM_ID and VERCEL_PROJECT_ID with no value. dotenv loads a bare KEY= as an empty string rather than omitting the key, and the schema marked those fields optional while validating them with min(1) — so "" counted as present and failed. Since readConfig uses EnvSchema.parse rather than safeParse, the result was fatal: the API exited during import with a ZodError naming all four keys.

OptionalUrl in the same file already normalized blank strings to undefined, so the intended behavior was established and these fields were simply missing it. This extracts that normalization as blankToUndefined, reuses it for OptionalUrl, and adds OptionalNonEmptyString for the affected optional credentials, including FACILITY_AWS_CODEBUILD_PROJECT, FACILITY_AWS_CODEBUILD_CACHE_BASE_LOCATION and PACKAGE_REGISTRY_TOKEN.

A whitespace-only value is still rejected rather than silently accepted, so a genuinely malformed credential does not slip through as "unset".

Closes #183

Test plan

  • pnpm --filter @facility/api exec vitest run test/config.test.ts — 15 passed, including a new case asserting that blank Vercel credentials from a copied .env.example resolve to undefined
  • Confirmed the previous schema rejected "" (too_small) while accepting undefined and a real value, and that the new schema accepts "" as unset
  • Confirmed dotenv reports all four keys as present=true value="" when parsing the committed .env.example
  • API boots from a copied .env.example and reports {"ok":true,"version":"0.3.0","db":"ok"} on /health
  • Confirm no deployment relied on a blank VERCEL_* value being a validation error

Scope note

This deliberately does not change .env.example. Removing the blank keys would hide the same class of failure for anyone who writes KEY= by hand, and the schema is the correct place to decide that "present but empty" means "unset". Happy to also drop the blank lines from the example file if you would rather have both.

@mianawais78

Copy link
Copy Markdown
Author

Reproduction detail

Verified on Node v22.23.2 with zod 4.4.3, from ae68401.

1. dotenv keeps blank keys. Parsing the committed .env.example:

VERCEL_TOKEN         present=true value=""
VERCEL_OIDC_TOKEN    present=true value=""
VERCEL_TEAM_ID       present=true value=""
VERCEL_PROJECT_ID    present=true value=""

2. The old schema rejected that. Comparing the previous and new definitions against the three possible inputs:

before | absent (undefined)  -> ok
before | blank ("")          -> FAIL: Too small: expected string to have >=1 characters
before | real value          -> ok
after  | absent (undefined)  -> ok
after  | blank ("")          -> ok
after  | real value          -> ok

3. It was fatal, not cosmetic. Because readConfig calls EnvSchema.parse, the API process exited during import rather than starting with the credential unset:

services\api\src\config.ts:187
  const parsed = EnvSchema.parse(env);
                           ^
ZodError: [ ... "path": [ "VERCEL_TOKEN" ] ... ]
    at readConfig (services\api\src\config.ts:187:28)
    at <anonymous> (services\api\src\dev.ts:4:16)

All four keys were reported together, so the failure is not specific to one of them.

One reviewer question: I kept min(1) after normalization so a whitespace-only value such as VERCEL_TOKEN=" " still fails loudly, on the assumption that it indicates a broken deployment rather than an intentionally unset credential. If you would rather treat whitespace as unset too, that is a one-line change to blankToUndefined.

`.env.example` ships `VERCEL_TOKEN`, `VERCEL_OIDC_TOKEN`, `VERCEL_TEAM_ID`
and `VERCEL_PROJECT_ID` with no value, and dotenv loads a bare `KEY=` as
an empty string rather than omitting it. The schema marked those fields
optional but validated them with `min(1)`, so an empty string counted as
present and failed. `readConfig` parses instead of safe-parsing, which
means the API crashed at import time with a `ZodError` naming all four
keys. Copying the example file, the documented first step of the
quickstart, therefore produced an instance that could not boot.

Normalize blank strings to `undefined` before validation so an unset
credential stays unset, while a value that is only whitespace is still
rejected rather than silently accepted.

Co-authored-by: Cursor <cursoragent@cursor.com>
@mianawais78
mianawais78 force-pushed the fix/blank-optional-env-values branch from 0f275ac to 3683d9a Compare August 19, 2026 00:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

API fails to boot from a copied .env.example: blank VERCEL_* values rejected by config schema

1 participant