Skip to content

fix(client): stop loading dotenv from library code - #69

Draft
apucacao wants to merge 1 commit into
mainfrom
alexis/no-dotenv-in-library
Draft

apucacao wants to merge 1 commit into
mainfrom
alexis/no-dotenv-in-library

Conversation

@apucacao

Copy link
Copy Markdown
Contributor

What the import did

packages/client/src/lifecycle.ts ran import 'dotenv/config' at module load. Because every @launchdarkly/ai-* package depends on @launchdarkly/ai-server, importing any of them loaded whatever .env sat in the consumer's current working directory into process.env before the consuming app's own code ran.

Why that is a bug for consumers

  • cwd-dependent. Which file gets loaded depends on where the process was started, not on anything the app declared.
  • Silent. Nothing is logged and there is no option to turn it off.
  • Fills unset credentials. dotenv never overrides existing values, so what it injects is precisely the set of variables the app deliberately left unset, including credentials like LD_API_KEY.

This bit a downstream consumer (Actuator): a stale token from an unrelated .env in its working directory was injected on import and LaunchDarkly returned 401. The app had not asked for .env loading and had no way to see it happen.

Loading .env is an application decision. It belongs in entrypoints (main.ts, examples/), where the author opts in, not in library code.

What changed

  • Removed the side-effect import from lifecycle.ts. The module reads process.env directly and needed nothing else from dotenv.
  • Dropped dotenv from @launchdarkly/ai-server's dependencies. It stays as a root devDependency for main.ts and examples/. yarn.lock is unchanged.
  • Replaced the test's vi.mock('dotenv/config') stub with a guard that fails if lifecycle.ts (or anything it imports) ever pulls dotenv back in. Verified it fails with the import present and passes without it.
  • Added a note to the root README and the client README that the SDK reads process.env only and does not load .env.

How consumers opt in

Add import 'dotenv/config'; to your own entrypoint before importing the SDK. The README examples already do this.

Verification

yarn install --frozen-lockfile, yarn build, yarn typecheck, yarn test (824 tests across 8 packages, all passing), yarn code:check, yarn lint:pkg.

🤖 Generated with Claude Code

`packages/client/src/lifecycle.ts` ran `import 'dotenv/config'` at module
load, so every `@launchdarkly/ai-*` package (all of them depend on
`@launchdarkly/ai-server`) silently loaded whatever `.env` sat in the
consumer's current working directory into `process.env` before the app's
own code ran.

A library must not do that:

- It is cwd-dependent: which file gets loaded depends on where the process
  was started, not on anything the consuming app declared.
- It is silent: nothing is logged and no option controls it.
- It fills in unset variables: dotenv does not override existing values, so
  the vars it injects are exactly the ones the app had deliberately left
  unset, including credentials such as `LD_API_KEY`.

A downstream consumer hit this in practice: a stale token from an unrelated
`.env` was injected on import and LaunchDarkly returned 401.

Changes:

- Remove the side-effect import from `lifecycle.ts`. The module already reads
  `process.env` directly and needs nothing else from dotenv.
- Drop `dotenv` from the client package's `dependencies`. It stays as a root
  devDependency because `main.ts` and `examples/` are application
  entrypoints and may keep opting in.
- Replace the test's `vi.mock('dotenv/config')` stub with a guard that fails
  if `lifecycle.ts` (or anything it imports) ever pulls dotenv back in.
- Note in both READMEs that the SDK reads `process.env` only, and that apps
  wanting `.env` loading add `import 'dotenv/config'` to their own
  entrypoint before importing the SDK.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

1 participant