Skip to content

fix(varlock): report load errors before resolving values - #1072

Merged
theoephraim merged 2 commits into
mainfrom
fix-resolve-on-failed-load
Sep 7, 2026
Merged

fix(varlock): report load errors before resolving values#1072
theoephraim merged 2 commits into
mainfrom
fix-resolve-on-failed-load

Conversation

@theoephraim

Copy link
Copy Markdown
Member

Alternative to #1069, same crash, fixed a level up.

What happens

finishLoad() bails before processing config items when any source failed to load/parse or a plugin failed to install. That leaves every item untyped, so resolving the graph hits expected dataType to be set in ConfigItem.resolve(). Because resolveEnvValues() starts each item without awaiting it and never used its reject(), the throw escaped as an unhandledRejection while the promise the caller awaited hung forever.

That assertion is a real invariant guard, not a user-facing error, so it stays as a plain Error. The bug is that we resolve a graph we already know failed to load.

Reproduced through the public API on main:

Error: expected dataType to be set
    at ConfigItem.resolve (.../env-graph.mjs)
    at async resolveItem (.../env-graph.mjs)

Why callers hit it

Most CLI commands (load, run, explain, reveal, proxy, plugin) already call checkForSchemaErrors() before resolving, which is why the crash never showed up there. Four call sites didn't:

  • varlock's exported load() resolved first and only ran checkForConfigErrors() afterwards, which never reports source-level parse errors
  • encrypt --file, keychain import, and scan

Change

  • Those four now run checkForSchemaErrors() before resolving. The user gets the parse error that actually caused the failure, with file/line, instead of a crash.
  • EnvGraph.configItemsProcessed records whether finishLoad() got as far as typing items; resolveEnvValues() throws immediately if it didn't, so a call site that forgets fails with an actionable message rather than somewhere deep in coercion.
  • resolveItem() rejections are routed to the deferred's reject(), so an unexpected throw settles the promise instead of hanging the caller.
  • scan's ad-hoc loadingError loop is replaced by checkForSchemaErrors(), which covers the same conditions finishLoad() bails on and prints the standard output.

Tests

packages/varlock/src/env-graph/test/failed-load-resolve.test.ts covers a failed load (rejects, no floating rejection) and an unexpected throw mid-resolution (rejects rather than hanging). Verified both fail without the fix.

Closes #1069. Thanks @hassaans for the diagnosis and repro.

finishLoad() bails before processing config items when a source failed to
load/parse or a plugin failed to install, which leaves every item untyped.
Resolving that graph hit the `expected dataType to be set` invariant deep in
ConfigItem.resolve() - and since resolveEnvValues() starts each item without
awaiting it and never used its reject(), the throw escaped as an
unhandledRejection while the caller's promise hung forever.

The assertion is a real invariant, so it stays. Instead:

- `varlock`'s exported load(), plus the encrypt/keychain/scan commands, now
  run checkForSchemaErrors() before resolving, like the other CLI commands
  already do. The user sees the parse error that actually caused the failure.
- resolveEnvValues() guards on a new EnvGraph.configItemsProcessed flag, so a
  call site that forgets fails immediately with an actionable message.
- resolveItem() rejections are routed to reject(), so any future unexpected
  throw settles the promise instead of hanging.
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

bumpy-frog

The changes in this PR will be included in the next version bump.

patch Patch releases

  • @varlock/native-helper-darwin 1.18.0 → 1.18.1
  • @varlock/native-helper-linux-arm64 1.18.0 → 1.18.1
  • @varlock/native-helper-linux-x64 1.18.0 → 1.18.1
  • @varlock/native-helper-win32-x64 1.18.0 → 1.18.1
  • varlock 1.18.0 → 1.18.1

Bump files in this PR

Click here if you want to add another bump file to this PR


This comment is maintained by bumpy.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size

⚠️ grows the bundle by 2.9 KB (+0.1%)

Metric main This PR Δ
Total dist 4487.2 KB 4490.1 KB +2.9 KB (+0.1%)
JS 1704.0 KB 1704.2 KB +0.1 KB (+0.0%)
Sourcemaps 2672.1 KB 2674.3 KB +2.2 KB (+0.1%)
Type defs 111.0 KB 111.6 KB +0.6 KB (+0.5%)
Other 0.0 KB 0.0 KB

dist/ only; native binaries are versioned separately and not counted here.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The failed-load fix is sound, but the exported load() also gains an unrelated breaking behavior for empty configurations.

Reviewed changes in 4ce89c30: failed graph guards, promise rejection propagation, CLI and library caller ordering, and regression coverage.

  • Failed-load guard: Tracks whether config items were processed and prevents resolution of a graph left half-built by a source or plugin load failure.
  • Promise settlement: Routes unexpected item-resolution throws to the promise returned by resolveEnvValues().
  • Caller ordering: Checks schema errors before resolution in encrypt --file, keychain import, scan, and package-root load().
  • Regression coverage: Exercises failed graph resolution and unexpected asynchronous resolution throws.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using azure/gpt-5.6-sol𝕏

// graph half-built, and resolving it produces confusing downstream errors rather than the
// parse error that actually caused them (same order the CLI commands use)
checkForSchemaErrors(envGraph);
checkForNoEnvFiles(envGraph);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkForNoEnvFiles() is unrelated to malformed-load handling and changes the exported programmatic load() from a successful empty initialization into CLI-formatted stderr output followed by a rejected CliExitError. Please remove this check, or explicitly make strict non-empty validation opt-in, since an empty graph already completes finishLoad() and safely resolves no keys.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving checkForNoEnvFiles() in. The exported load() is undocumented and has no callers anywhere (the load() hits in the docs are all the codegen'd Java/Rust/PHP Env.load(), unrelated), so we're treating it as internal and matching CLI behavior rather than preserving a silent empty-graph success nobody depends on.

The comment did point at something worth fixing though: the internal export offers loadVarlockEnvGraph and checkForConfigErrors but not checkForSchemaErrors, so a consumer following that surface writes exactly the sequence this PR fixes. Added it in e2e1d00.

@pkg-pr-new

pkg-pr-new Bot commented Sep 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

@env-spec/parser

npm i https://pkg.pr.new/dmno-dev/varlock/@env-spec/parser@1072

varlock

npm i https://pkg.pr.new/dmno-dev/varlock@1072

@varlock/native-helper-darwin

npm i https://pkg.pr.new/dmno-dev/varlock/@varlock/native-helper-darwin@1072

@varlock/native-helper-linux-arm64

npm i https://pkg.pr.new/dmno-dev/varlock/@varlock/native-helper-linux-arm64@1072

@varlock/native-helper-linux-x64

npm i https://pkg.pr.new/dmno-dev/varlock/@varlock/native-helper-linux-x64@1072

@varlock/native-helper-win32-x64

npm i https://pkg.pr.new/dmno-dev/varlock/@varlock/native-helper-win32-x64@1072

@varlock/aws-sigv4-plugin

npm i https://pkg.pr.new/dmno-dev/varlock/@varlock/aws-sigv4-plugin@1072

commit: e2e1d00

The internal surface hands consumers loadVarlockEnvGraph and
checkForConfigErrors but not checkForSchemaErrors, so following it leads
straight to the sequence this PR fixes - resolving a graph that failed to
load, with only a check that never reports source-level parse errors.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in the incremental change. The existing review thread remains open.

Reviewed changes since the prior Pullfrog review: the internal API was extended to expose the required pre-resolution schema check.

  • Internal validation API: Exported checkForSchemaErrors alongside loadVarlockEnvGraph and checkForConfigErrors, so low-level consumers can surface failed-load errors before calling resolveEnvValues().

Pullfrog  | Fix it ➔View workflow run | Using azure/gpt-5.6-sol𝕏

@theoephraim
theoephraim merged commit 16f801f into main Sep 7, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant