Skip to content

Stop deleting runtime env vars when booting from a build-baked snapshot - #1053

Closed
theoephraim wants to merge 13 commits into
mainfrom
build-baked-env-conflict-guard
Closed

Stop deleting runtime env vars when booting from a build-baked snapshot#1053
theoephraim wants to merge 13 commits into
mainfrom
build-baked-env-conflict-guard

Conversation

@theoephraim

@theoephraim theoephraim commented Aug 31, 2026

Copy link
Copy Markdown
Member

Fixes a destructive silent failure reported from production: a Next.js standalone container passing env vars at boot (docker run -e REDIS_URL=...) had those vars deleted from process.env by varlock at startup, taking the service down.

Root cause

In a container where the varlock CLI is unreachable, the standalone server falls back to the env blob baked into the bundled runtime at build time. initVarlockEnv treated that blob like a fresh resolution, so items that resolved to undefined at build time (REDIS_URL= in the schema, no value in the build env) triggered the stale-echo cleanup from #1038, deleting the runtime-provided value. That cleanup assumes a resolution happened in this process (a genuine ambient value would have acted as an override and resolved to it), which is false for a blob resolved on a build machine.

The deletion shipped only in varlock@1.17.1; 1.17.0 and earlier are unaffected.

Behavior change

Scoped deliberately to the regression plus visibility, so no currently-booting deployment breaks:

  • Runtime-provided vars are never deleted on a baked boot. This is the fix.
  • The baked snapshot stays authoritative for ENV, as before: it was resolved at build time, and runtime values cannot be validated or coerced against it (the blob carries no schema info).
  • A runtime value that differs from the snapshot is logged as a warning naming the keys, instead of being ignored in silence. The boot is never blocked, and absence is not a mismatch, so blob-only deploys (serverless, deliberate bake-into-image) are untouched.
  • Values that cannot be verified (coerced values with no recorded raw pre-coercion form, e.g. sensitive FLAG=YEStrue) are skipped rather than reported.

Enforcing the mismatch (failing the boot) was deliberately left out: silent-ignore of runtime env on a baked boot predates this bug and exists in every released version, and failing on it would take down deployments that work today (docker sets HOSTNAME in every container; promoted images and fly secrets set drift the same way). That belongs with varlock freeze (#1049), where baking becomes explicit and the contract can be enforced honestly.

Mechanics

  • The injection preludes (nextjs webpack + turbopack, vite resolved-env SSR entry) bake an injectedAtBuild: true flag INSIDE the serialized payload, before encryption where applicable. Provenance therefore travels with the blob to child/worker processes and through encryption round-trips, and can never outlive it: any fresh resolution produces an unflagged blob, so no marker clearing is needed anywhere.
  • For the nextjs preludes a blob already present in the runtime env (varlock run) wins over the baked fallback, as before. The vite resolved-env payload stays authoritative by design: that artifact is frozen on purpose, and its static values are inlined at build time, so honoring a boot-time blob would override only the runtime-resolved subset.
  • The @next/env compat does not trust a flagged blob as its cached env and attempts a real load instead, covering runtimes that load before the compat (e.g. via instrumentation.js).
  • Fixes a pre-existing crash on the side: in init-only mode an encrypted ambient blob (varlock run --inject blob under @encryptInjectedEnv) reached initVarlockEnv as ciphertext and threw. The vite prelude now decrypts it, mirroring the nextjs init-server behavior.

Testing

  • Unit coverage for the no-delete fix, the mismatch warning, unverifiable coerced values, and unflagged (freshly resolved) blobs.
  • Framework-test scenarios booting a real standalone build copied outside the project tree with a stripped environment (mimicking a container image), per bundler. Vite scenarios cover the mismatch warning, an ambient blob (baked payload still wins), and the init-only encrypted-blob decrypt.
  • Green on Next 14/15/16 and the full vite and nuxt suites.

Docs

The environments guide now clarifies that process-env overrides apply at resolution time; the Next.js, vite, nuxt, and Docker pages describe what a baked boot does with runtime env and how to make runtime values apply.

Follow-ups (separate PRs, discussed): land varlock freeze (#1049) as the single explicit pinning mechanism, then rework the integrations so baking is explicit or platform-automatic, and enforce the frozen contract there.

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

bumpy-frog

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

minor Minor releases

  • @varlock/astro-integration 1.4.0 → 1.5.0 (cascade)
  • @varlock/cloudflare-integration 1.5.0 → 1.6.0 (cascade)
  • @varlock/native-helper-darwin 1.17.1 → 1.18.0
  • @varlock/native-helper-linux-arm64 1.17.1 → 1.18.0
  • @varlock/native-helper-linux-x64 1.17.1 → 1.18.0
  • @varlock/native-helper-win32-x64 1.17.1 → 1.18.0
  • @varlock/nextjs-integration 1.2.1 → 1.3.0
  • @varlock/nuxt-integration 0.1.0 → 0.2.0 (cascade)
  • @varlock/vite-integration 1.5.0 → 1.6.0
  • varlock 1.17.1 → 1.18.0

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 Aug 31, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size

⚠️ grows the bundle by 23.2 KB (+0.5%)

Metric main This PR Δ
Total dist 4289.5 KB 4312.7 KB +23.2 KB (+0.5%)
JS 1645.4 KB 1653.4 KB +7.9 KB (+0.5%)
Sourcemaps 2548.4 KB 2562.4 KB +14.1 KB (+0.6%)
Type defs 95.7 KB 96.9 KB +1.2 KB (+1.2%)
Other 0.0 KB 0.0 KB

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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 31, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
varlock-website 277627e Commit Preview URL

Branch Preview URL
Sep 01 2026, 03:49 AM

@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 build provenance marker can outlive the blob it describes, so fresh runtime resolutions can be rejected as if they were baked snapshots.

Reviewed changes Reviewed the build-baked snapshot guard across the shared runtime, Next.js and Vite injection paths, standalone framework scenarios, and deployment documentation.

  • Conflict guard Detects ambient schema values that differ from a marked build snapshot, with an explicit warning-only escape hatch.
  • Injection provenance Marks Next.js webpack, Turbopack, and Vite resolved-env fallbacks and mirrors provenance to child processes.
  • Fresh Next resolution Makes @next/env retry marked snapshots and clears provenance after a successful CLI load.
  • Regression coverage Adds runtime unit tests and standalone Next.js boot scenarios for conflicting, blob-only, and varlock run deployments.
  • User guidance Documents resolution-time overrides, container behavior, the recommended runtime entrypoint, and the escape hatch.

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

Comment thread packages/varlock/src/runtime/env.ts Outdated
@pkg-pr-new

pkg-pr-new Bot commented Aug 31, 2026

Copy link
Copy Markdown

Open in StackBlitz

varlock

npm i https://pkg.pr.new/varlock@1053

@varlock/astro-integration

npm i https://pkg.pr.new/@varlock/astro-integration@1053

@varlock/cloudflare-integration

npm i https://pkg.pr.new/@varlock/cloudflare-integration@1053

@varlock/nextjs-integration

npm i https://pkg.pr.new/@varlock/nextjs-integration@1053

@varlock/nuxt-integration

npm i https://pkg.pr.new/@varlock/nuxt-integration@1053

@varlock/vite-integration

npm i https://pkg.pr.new/@varlock/vite-integration@1053

@varlock/native-helper-darwin

npm i https://pkg.pr.new/@varlock/native-helper-darwin@1053

@varlock/native-helper-linux-arm64

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

@varlock/native-helper-linux-x64

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

@varlock/native-helper-win32-x64

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

commit: 277627e

…apshot

When a server boots from the env blob baked into build output (e.g.
Next.js standalone in a container where the varlock CLI is unreachable),
initVarlockEnv treated the blob like a fresh resolution: runtime env
vars for schema-declared keys were silently ignored, and items that
resolved to undefined at build time DELETED the corresponding runtime
values from process.env. A schema item like REDIS_URL= would actively
clear docker-run-provided values at boot, silently breaking the service.

The baked snapshot stays authoritative, but runtime values are now
checked against it. Absence is not divergence: blob-only deploys
(serverless, deliberate bake-into-image) see no change. A runtime value
that CONFLICTS with the snapshot is evidence of misconfiguration
(config being supplied that cannot be validated or applied), so the
boot fails loudly, naming the keys and pointing at varlock run, which
re-resolves against the runtime env with full validation. Setting
_VARLOCK_ALLOW_BAKED_ENV_CONFLICTS=1 downgrades the failure to a
warning and boots on the baked values, without deleting runtime vars
from process.env.

Mechanics: the injection preludes (nextjs webpack/turbopack, vite
resolved-env SSR entry) mark the blob via
globalThis.__varlockEnvInjectedAtBuild only when the baked fallback
actually engages; the marker is mirrored to
_VARLOCK_ENV_INJECTED_AT_BUILD so child/worker processes inheriting the
blob apply the same semantics; the @next/env compat skips the cached
blob and clears the marker when it can re-resolve (covers runtimes
loading before the compat, e.g. via instrumentation).

Framework tests gain three standalone-container scenarios per bundler:
conflicting runtime env fails loudly, blob-only boots serve baked
values, and a varlock run entrypoint resolves runtime env properly.
Docs now state that process-env overrides apply at resolution time and
how baked deployments differ.
@theoephraim
theoephraim force-pushed the build-baked-env-conflict-guard branch from 1a850dd to 91d3a97 Compare August 31, 2026 07:44
__VARLOCK_ENV_INJECTED_AT_BUILD (double underscore: set by varlock) is
the child-process mirror of the baked-fallback marker;
_VARLOCK_ALLOW_BAKED_ENV_CONFLICTS (single underscore: user-settable)
is the conflict-guard escape hatch. Both added to the reserved-vars
registries and the reserved-variables docs page.
Snapshot-neutral name that stays correct when explicit freezing replaces
implicit build-time baking.

@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 critical new issues in this delta; one stale documentation anchor is noted inline.

Reviewed changes Reviewed the escape-hatch rename since the prior Pullfrog review and rechecked it against the complete conflict-guard change.

  • Escape hatch naming Renamed the warning-only override to _VARLOCK_ALLOW_ENV_SNAPSHOT_CONFLICTS across runtime behavior, tests, reserved-variable registration, deployment guidance, and release notes.
  • Existing conflict behavior Preserved the build-snapshot conflict detection, warning path, and process environment handling while changing the public variable name.

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

Comment thread packages/varlock-website/src/content/docs/reference/reserved-variables.mdx Outdated
…rkers

The globalThis/env-var marker could outlive the blob it described: fresh
resolutions (auto-load, vite reload) replaced the blob without clearing it,
misclassifying validated runtime data as a baked snapshot. The injectedAtBuild
flag now lives inside the payload itself, so provenance travels with the blob
(child processes, encryption round-trips) and a fresh resolution inherently
clears it. Also skip conflict-flagging values whose raw pre-coercion form was
never recorded (sensitive coerced overrides, file-sourced coerced values),
since they cannot be verified against the ambient env.

@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 payload-scoped provenance fix resolves the prior stale-marker issue, but Vite's baked payload still overrides the fresh blob supplied by the documented varlock run remedy.

Reviewed changes Reviewed the payload-provenance rewrite since the prior Pullfrog review and rechecked its Next.js, Vite, encryption, runtime, test, and documentation effects.

  • Payload provenance Moved injectedAtBuild into the serialized graph so provenance follows the exact payload through encryption and child processes.
  • Fresh resolution handling Updated Next.js compatibility loading to reject flagged cache data and naturally replace it with an unflagged fresh resolution.
  • Conflict matching Avoided false conflicts for coerced values whose raw source form was not serialized.
  • Regression coverage Reworked runtime tests around payload-local provenance and confirmed unflagged fresh blobs retain existing semantics.
  • Internal cleanup Removed the process marker registration and its obsolete documentation.

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

Comment thread packages/integrations/vite/src/index.ts

@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 this delta. The existing Vite runtime-blob precedence thread remains open because this scenario does not exercise a fresh __VARLOCK_ENV supplied by varlock run.

Reviewed changes Reviewed the Vite framework-test addition since the prior Pullfrog review and checked it against the complete snapshot-conflict implementation.

  • Vite conflict scenario Added an end-to-end resolved-env SSR build that boots with a conflicting ambient value and verifies a nonzero exit plus the conflict diagnostic and varlock run guidance.
  • Existing Vite precedence concern Confirmed the new scenario launches without __VARLOCK_ENV, so it validates the baked-snapshot guard but does not change or cover the previously reported fresh-runtime-blob precedence path.

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

…plicit bake warns

injectedAtBuild becomes a mode: 'fallback' (Next.js standalone implicit bake,
where nobody chose baking and a conflicting runtime value is a
misconfiguration -> boot fails) vs 'explicit' (vite resolved-env, where
bake-into-build is the declared contract and a stray runtime value must not
kill a working deploy -> loud warning, boots on baked values). Both modes
still suppress the stale-echo cleanup so runtime vars are never deleted.

@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 new warning-only contract for explicit Vite and Nuxt snapshots is not reflected in the public deployment guidance, which still says these conflicts fail the boot.

Reviewed changes Reviewed the explicit-versus-fallback snapshot behavior introduced since the prior Pullfrog review.

  • Split baked intent Replaced the boolean provenance marker with fallback and explicit modes so implicit Next.js snapshots fail on conflicts while chosen Vite snapshots warn.
  • Changed explicit boot behavior Kept explicit baked values authoritative, preserved conflicting ambient values, and allowed the process to continue after logging the conflict.
  • Updated regression coverage Changed the Vite end-to-end scenario and added runtime assertions for warning-only explicit snapshots.
  • Updated release notes Documented the distinct behavior of explicit Vite baking in the package changeset.

⚠️ Deployment guidance still promises fail-closed behavior

The environments, Docker, and reserved-variable pages say a conflicting runtime value fails any baked-snapshot boot. A Vite or Nuxt deployment using the newly explicit resolved-env mode now logs a warning and continues, so operators following those pages may believe a conflicting deployment was stopped when it was not.

Technical details
# Document the explicit snapshot contract

## Affected sites
- `packages/varlock-website/src/content/docs/guides/environments.mdx:22` - describes failure as the general behavior for framework-baked snapshots
- `packages/varlock-website/src/content/docs/integrations/docker.mdx:54-56` - includes Vite SSR in the baked-image topology, then says every conflict fails
- `packages/varlock-website/src/content/docs/reference/reserved-variables.mdx:69` - presents the escape hatch and fail-closed behavior without limiting them to fallback snapshots
- `packages/varlock-website/src/content/docs/integrations/vite.mdx:121` - documents `resolved-env` without its new conflict behavior
- `packages/varlock-website/src/content/docs/integrations/nuxt.mdx:195` - documents `resolved-env` without its new conflict behavior

## Required outcome
- Distinguish implicit fallback snapshots, which throw unless the escape hatch is enabled, from explicit Vite and Nuxt `resolved-env` snapshots, which warn and continue on baked values.
- Make the scope of `_VARLOCK_ALLOW_ENV_SNAPSHOT_CONFLICTS` clear, since it does not change explicit-mode behavior.

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

…ocs scope

A blob injected at boot (varlock run) now wins over the baked payload, matching
the Next.js preludes, so the conflict warning's varlock run remedy actually
works for resolved-env artifacts. Docs now say explicit bakes warn on conflicts
rather than failing the boot.

@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 plaintext Vite precedence fix works, but a supported encrypted runtime-blob path still fails before initialization.

Reviewed changes Reviewed the Vite runtime-blob precedence fix and documentation corrections added since the prior Pullfrog review.

  • Fresh Vite precedence Deferred both plaintext and encrypted baked payloads when a runtime __VARLOCK_ENV is already present.
  • Regression coverage Added a Vite SSR scenario proving that a fresh plaintext runtime blob controls runtime-resolved reads without a baked-snapshot warning.
  • Documentation scope Distinguished explicit Vite baking from implicit fallback baking in the environments and reserved-variable references.

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

Comment thread packages/integrations/vite/src/index.ts Outdated
varlock run --inject blob under @encryptInjectedEnv hands the child a
varlock:v1: ciphertext blob; now that the baked payload defers to an ambient
blob, the prelude must decrypt it in place (mirroring nextjs init-server)
instead of letting initVarlockEnv throw on ciphertext.

@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 this delta. The encrypted runtime-blob failure is fixed; the previously reported deployment-documentation scope concern remains outstanding.

Reviewed changes Reviewed the encrypted ambient-blob fix added since the prior Pullfrog review and rechecked it against the complete snapshot-conflict implementation.

  • Ambient blob decryption Decrypted a fresh varlock:v1: process blob before initVarlockEnv() so encrypted varlock run --inject blob data remains authoritative over the baked Vite payload.
  • Regression coverage Added a Vite SSR scenario that supplies an encrypted ambient blob and verifies successful initialization without a snapshot-conflict warning.

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

@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 this delta. The general, Docker, and Vite guidance now reflects the explicit snapshot behavior; the previously reported Nuxt documentation gap remains outstanding.

Reviewed changes Reviewed the deployment-documentation corrections added since the prior Pullfrog review and rechecked them against the complete snapshot-conflict implementation.

  • Explicit bake guidance Distinguished implicit fallback snapshots, which fail on conflicts, from explicit Vite resolved-env snapshots, which warn and continue on baked values.
  • Deployment references Updated the environments guide, Docker topology, Vite integration page, and reserved-variable reference with the warning-only explicit behavior and fresh-blob precedence.

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

@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 found.

Reviewed changes Reviewed the Nuxt documentation correction added since the prior Pullfrog review and checked it against the current Nuxt and Vite integration behavior.

  • Nuxt explicit-bake guidance Documented that resolved-env conflicts warn and continue on baked values, while a fresh runtime __VARLOCK_ENV blob takes precedence.

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

…ambient blob

resolved-env bakes config into the artifact on purpose, and its static values
are inlined at build time - deferring to a boot-time blob would override only
the runtime-resolved subset, leaving the artifact reading from two
resolutions. Reverts the precedence change; the explicit-mode conflict message
now says so instead of suggesting varlock run. The ambient-blob decryption
stays: with no baked payload (init-only) an encrypted blob from
'varlock run --inject blob' would otherwise reach init as ciphertext.

@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 new Vite precedence contract still processes an ambient encrypted blob that should be ignored, so irrelevant ciphertext can abort a resolved-env artifact.

Reviewed changes Reviewed the Vite resolved-env precedence reversal since the prior Pullfrog review.

  • Frozen payload precedence Made the baked Vite payload authoritative over an ambient runtime blob to keep static and runtime-resolved values on one resolution.
  • Mode-specific encrypted blobs Moved encrypted ambient-blob coverage to init-only, where the runtime blob remains the selected source.
  • Deployment guidance Updated Vite, Nuxt, and Docker guidance to state that varlock run does not replace a resolved-env payload.

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

lines.push(`globalThis.__varlockEncryptedEnv = ${JSON.stringify(encrypted)};`);
} else {
lines.push(`globalThis.__varlockLoadedEnv = ${JSON.stringify(varlockLoadedEnv)};`);
lines.push(`globalThis.__varlockLoadedEnv = ${serialized};`);

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.

Making the baked global unconditional means an ambient __VARLOCK_ENV is no longer a selected source in resolved-env, but the shared block below still decrypts any varlock:v1: ambient value and can throw for a missing key, wrong key, or malformed ciphertext before initialization. Skip ambient-blob decryption when the baked payload is authoritative, and cover resolved-env with encrypted invalid or keyless ambient data.

Technical details
# Ignore ambient ciphertext in resolved-env mode

## Affected sites
- `packages/integrations/vite/src/index.ts:425-430` - installs the authoritative baked payload on `globalThis`
- `packages/integrations/vite/src/index.ts:461-465` - still validates and decrypts the now-ignored process blob in every non-auto-load mode
- `framework-tests/frameworks/vite/vite-shared.ts:415-464` - covers plaintext ambient data under `resolved-env` and ciphertext only under `init-only`

## Required outcome
- A `resolved-env` artifact must initialize from its baked payload regardless of an irrelevant ambient `__VARLOCK_ENV` value.
- `init-only` must continue decrypting its selected ambient blob and rejecting unusable ciphertext.

…g the boot

The reported bug is that 1.17.1 DELETES runtime-provided env vars on a baked
boot. Failing the boot on a mismatch was new enforcement of a condition that
has been silently true in every version, and it can take down deployments that
work today (docker sets HOSTNAME in every container; promoted images and
'fly secrets set' drift the same way) - including the reporter, who is restored
by the delete fix alone but would stay down under a throw.

Baked values stay authoritative for ENV, runtime-provided vars are never
deleted, and a mismatch is logged so it is visible rather than silent. With
nothing throwing, the fallback/explicit mode split and the
_VARLOCK_ALLOW_ENV_SNAPSHOT_CONFLICTS escape hatch are both unnecessary and
are removed. Proper enforcement belongs with freeze.
@theoephraim theoephraim changed the title Fail loudly when runtime env conflicts with a build-baked env snapshot Stop deleting runtime env vars when booting from a build-baked snapshot Sep 1, 2026

@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 warning-only contract leaves one build scenario stale and introduces one inaccurate deployment claim.

Reviewed changes Reviewed the warning-only baked-snapshot behavior introduced since the prior Pullfrog review.

  • Warning-only conflicts Replaced fallback and explicit provenance modes with a single build-baked marker and changed every snapshot mismatch to log without blocking startup.
  • Runtime preservation Kept runtime values for items that were undefined in the baked snapshot while retaining baked values as authoritative for ENV and defined process.env entries.
  • Contract cleanup Removed the conflict escape hatch and updated release notes, deployment guidance, Vite coverage, and runtime unit tests for the new behavior.

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

}
if (conflictingKeys.length) {
// eslint-disable-next-line no-console
console.error([

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.

Changing this path to warning-only leaves the Next.js standalone scenario asserting the removed fail-closed behavior and old conflicts with... diagnostic. The build scenario will fail when enabled and no longer verifies that the server boots while preserving the runtime value for the undefined baked item.

Technical details
# Align the Next.js standalone scenario with warning-only behavior

## Affected sites
- `packages/varlock/src/runtime/env.ts:587` - now logs `Runtime environment differs` and continues
- `framework-tests/frameworks/nextjs/nextjs-shared.ts:615-653` - still describes a failed boot and requires the removed `conflicts with the build-time env snapshot` text

## Required outcome
- Update the standalone scenario to assert the warning, a successful response, baked `ENV` behavior, and preservation of the runtime-provided `process.env` value.


Use when a [framework integration](/integrations/overview/) (Next.js, Vite SSR, Cloudflare Workers, etc.) resolves and injects env during `build`. Varlock belongs in the **builder** stage; the runtime image receives env via the platform or an encrypted blob, not via the CLI.

The baked snapshot is authoritative: it was resolved at build time, so values passed in the container's runtime environment (e.g. `docker run -e REDIS_URL=...`) cannot be validated or applied to it, and `ENV` uses the baked values. If a runtime env var differs from the snapshot for a schema-declared key, varlock logs a warning naming the keys so the mismatch is visible instead of silent; the boot is not blocked, and runtime-provided vars are left untouched in `process.env`. To have runtime env values apply, boot through `varlock run` (see the Runtime injection tab), which re-resolves against the runtime environment with full validation and coercion. Blob-only deployments, where no schema values are set in the runtime env, are unaffected.

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.

“Runtime-provided vars are left untouched” is only true when that item was undefined in the baked snapshot. For a defined item, initVarlockEnv overwrites process.env with the baked value, and the new unit test explicitly asserts that behavior, so narrow this sentence to avoid promising preservation for all keys.

@theoephraim

Copy link
Copy Markdown
Member Author

Superseded by #1055, which is the regression fix alone (~250 lines vs 585).

The scope here drifted a long way past the reported bug: a mismatch warning, a next-env-compat cached-env change, an unrelated init-only vite decrypt fix, and docs describing a fail-closed design that the final commit removed. The framework tests on this branch still assert the removed throw (conflicts with the build-time env snapshot), so the branch no longer builds a coherent story.

#1055 keeps the one thing that was hard-won here: the injectedAtBuild flag lives inside the payload, not beside it. Everything else moves to varlock freeze (#1049) or its own PR.

@theoephraim theoephraim closed this Sep 1, 2026
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