Follow-up to #265: report unusable .env files and stop the dotenv hang - #269
Merged
Merged
Conversation
#265 stops the `IsADirectory` crash by requiring `.env` to be a regular file. That check is right, and this builds on it rather than replacing it. Two things it leaves open, both of which bite the setup in #264: A `.env` written to be `source`d by a shell hangs the CLI outright. @std/dotenv expands `$VAR` references in unquoted values using a `while` loop that never terminates when a value refers to itself, so a single ordinary line like `export PATH=$PATH:/opt/bin` spins forever at startup -- no error, no exit. That is a worse failure than the crash #265 fixes, it is still present in the latest @std/dotenv (0.225.8), and the reporter's files are exactly the shell-sourced kind that contain it. Since the only keys we ever apply are LINEAR_/GH_/GITHUB_, the fix is to drop every other line before parsing, which removes the whole class of failure and also silences the parser's warnings about keys that were never ours to complain about. An unquoted `$` reference in one of our own keys is refused with a warning instead of expanded -- unexpanded references otherwise resolve to the literal string "undefined", which is silent corruption of a config value. Quoted values are left alone, since dotenv takes those literally and they cannot hang. And skipping the file silently hides it. Both the issue and CLAUDE.md ask for the opposite: the reporter explicitly said being entirely silent "may hide deeper issues", and the project's rule is to never fail silently. So an unusable candidate now prints one yellow warning on stderr -- never stdout, so --json output and the completion scripts stay clean -- and the CLI continues. `LINEAR_IGNORE_ENV_FILE=1` opts out entirely, so the warning is self-terminating for a repo that will never have a dotenv-shaped .env. Also folded in: an unreadable .env (mode 000) passed #265's isFile check and then crashed in the read, so read and parse failures are caught too, and the repository-root candidate goes through the same path instead of a near-copy of it.
schpetbot
force-pushed
the
oss-pr-lander/265
branch
from
August 31, 2026 18:59
d551ac2 to
724a6dc
Compare
schpetbot
marked this pull request as ready for review
August 31, 2026 19:01
This was referenced Sep 2, 2026
Collaborator
Author
|
Shipped in v2.6.0. Follow-up to #265 (which fixed #264).
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #265 (which fixes #264). Draft until #265 lands — this branch currently contains @jackarch-2's commit too, and will be rebased down to just the delta once #265 is merged.
#265's
?.isFilecheck is right, and this builds on it rather than replacing it. Two things it leaves open, both of which hit the setup described in #264:The reporter's
.envfiles still hang the CLI#264 says their
.envfiles contain zsh syntax because engineerssourcethem. A single ordinary line in such a file hangslinearforever at startup — no error, no exit:@std/dotenvexpands$VARreferences in unquoted values with awhileloop that never terminates when a value refers to itself. This is worse than the crash #265 fixes (a crash at least prints something), and it's still present in the latest@std/dotenv(0.225.8), so a version bump doesn't help.Since the only keys we ever apply are
LINEAR_/GH_/GITHUB_, the fix is to drop every other line before handing the text to the parser. That removes the whole class of failure, and also silences the parser's per-lineconsole.warnabout invalid identifiers that were never ours to complain about. An unquoted$reference in one of our keys is refused with a warning rather than expanded — an unset reference otherwise resolves to the literal string"undefined", which is silent corruption of a config value. Quoted values are untouched, since@std/dotenvtakes those literally and they can't hang (verified against 0.225.6).Skipping the file silently hides it
#264 explicitly asks not to be silent — "being entirely silent about the issue may hide deeper issues" — and CLAUDE.md's rule is to never fail silently. So an unusable candidate now prints one yellow warning and continues:
Always stderr, never stdout, so
--jsonoutput and the completion scripts stay machine-readable (QA'd:completions zshemits 93KB of clean stdout with the warning on stderr).LINEAR_IGNORE_ENV_FILE=1opts out entirely, which is what keeps the warning from being a permanent per-invocation tax on a repo that will never have a dotenv-shaped.env.Also folded in
.env(mode000) passed feat: silently ignore.envdirectories when loading config #265'sisFilecheck and then crashed in the read. Read and parse failures are caught too..envwould have blocked the read forever;isFilecovers that, and it's now exercised by QA.Behavior change worth a look
Unquoted values are no longer shell-expanded. Expansion was already half-broken — unset references became
"undefined", self-referential ones hung — and every skipped key is warned about (unless the process environment already overrides it, in which case the.envvalue would have lost anyway), so nothing is dropped quietly.Verification
571 tests pass;
deno check,deno lint,deno fmt --checkclean. 17 provenance tests including all four pre-existing.envtests unchanged. Manually QA'd 15 cases against the real CLI: missing / valid / directory / shell-sourced / unreadable / FIFO / symlink / dangling symlink / not-a-git-repo / nested-falls-back-to-root / opt-out, plus stdout cleanliness.