Skip to content

fix(deploy): read server values from void/env in cron and queue jobs - #724

Merged
cpojer merged 1 commit into
voidzero-dev:mainfrom
morinokami:fix/job-handlers-worker-conditions
Nov 5, 2026
Merged

cpojer merged 1 commit into
voidzero-dev:mainfrom
morinokami:fix/job-handlers-worker-conditions

Conversation

@morinokami

Copy link
Copy Markdown
Contributor

In a framework app deployed with void deploy, a cron job or queue consumer that reads a server value from void/env fails:

// crons/charge.ts
import { env } from 'void/env';

export default async function () {
  const key = env.STRIPE_KEY;
  // ...
}
env: Key 'STRIPE_KEY' is not exposed to the client. Void only exposes VITE_* schema keys to browser code. Either rename the key in env.ts with that prefix, or move this access to a server module.

The same job works in vp dev, and /__void/ready returns 200, so the failure only shows up when the job runs after a deploy. The env argument that a handler receives is not affected.

generateWrapper compiles these jobs in a library build, which defaults to browser conditions, so void/env resolves to its client variant.

Fix

The build now uses Worker conditions plus production, as bundleEntry and prepareBundledBuildOutput do.

  • Dependencies with workerd or worker exports now get their Worker builds in jobs too, as they do in the app's own bundle. Among Void's own subpaths, only void/env has conditional exports like this.
  • The server variant of void/env imports node:async_hooks. By default, framework deploys give the Worker nodejs_als or nodejs_compat, so it loads.

Tests

packages/void/test/unit/wrapper.test.ts: D1 and PostgreSQL apps whose cron and queue jobs read a server value from void/env, run in workerd. The D1 case runs with only nodejs_als, as SvelteKit, Nuxt and Astro deploys do. The PostgreSQL case runs the jobs in a request scope. Both cases fail without this fix.

Verification

  • vp test run: 776 files pass, 1 skipped (16,003 tests pass, 29 skipped).
  • vp run check: 33/33 pass.
  • By hand on copies of the React Router, SvelteKit and Nuxt playgrounds: build, generateWrapper, then that framework's upload preparation, run in Miniflare under the flags a D1 deploy gives it. /__void/ready answers 200. A cron job and a queue consumer read STRIPE_KEY from void/env, and the cron job also reads a default through an imported env.ts.
  • By hand with vp dev on a copy of the React Router playground: a cron job reads its value from void/env.
  • Not run: void deploy against Cloudflare. The SvelteKit and Nuxt upload preparation used a minimal hand-written config.

🤖 Generated with Claude Code

The deploy wrapper's cron and queue bundle is a library build, which
defaults to browser conditions. `void/env` resolved to its client variant
there, so a deployed job that read a server key threw "is not exposed to
the client". The same job worked in `vp dev`, and `/__void/ready` still
returned 200, so the failure only showed up after a deploy.

The bundle now uses Worker conditions, as `bundleEntry` does. Dependencies
with `workerd` or `worker` exports also get their Worker builds in jobs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cpojer cpojer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍

@cpojer
cpojer merged commit d4c4b49 into voidzero-dev:main Nov 5, 2026
2 checks passed
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.

2 participants