diff --git a/packages/adapter-node/package.json b/packages/adapter-node/package.json
index 88db65370177..fe5615841250 100644
--- a/packages/adapter-node/package.json
+++ b/packages/adapter-node/package.json
@@ -35,11 +35,13 @@
"scripts": {
"dev": "rolldown -cw",
"build": "rolldown -c",
- "test": "vitest run",
+ "test": "pnpm test:unit && pnpm test:apps",
"check": "tsc",
"lint": "prettier --check .",
"format": "pnpm lint --write",
- "prepublishOnly": "pnpm build"
+ "prepublishOnly": "pnpm build",
+ "test:unit": "vitest run",
+ "test:apps": "pnpm build && pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test"
},
"devDependencies": {
"@polka/url": "catalog:",
diff --git a/packages/adapter-node/test/apps/instrumentation/.gitignore b/packages/adapter-node/test/apps/instrumentation/.gitignore
new file mode 100644
index 000000000000..762b1c80fa9d
--- /dev/null
+++ b/packages/adapter-node/test/apps/instrumentation/.gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+node_modules
+/.svelte-kit
+/build
diff --git a/packages/adapter-node/test/apps/instrumentation/package.json b/packages/adapter-node/test/apps/instrumentation/package.json
new file mode 100644
index 000000000000..35a1f2f8c7e6
--- /dev/null
+++ b/packages/adapter-node/test/apps/instrumentation/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "test-adapter-node-instrumentation",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "imports": {
+ "#lib/*": "./src/lib/*"
+ },
+ "scripts": {
+ "dev": "vite dev",
+ "build": "vite build",
+ "prepare": "svelte-kit sync || echo ''",
+ "test": "MY_BASE_URL=https://api.example pnpm build && node test/boot.js"
+ },
+ "devDependencies": {
+ "@sveltejs/kit": "workspace:^",
+ "@sveltejs/vite-plugin-svelte": "catalog:",
+ "svelte": "catalog:",
+ "vite": "catalog:"
+ }
+}
diff --git a/packages/adapter-node/test/apps/instrumentation/src/app.html b/packages/adapter-node/test/apps/instrumentation/src/app.html
new file mode 100644
index 000000000000..26bc25de4e52
--- /dev/null
+++ b/packages/adapter-node/test/apps/instrumentation/src/app.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ %sveltekit.head%
+
+
+
+ %sveltekit.body%
+
+
diff --git a/packages/adapter-node/test/apps/instrumentation/src/env.js b/packages/adapter-node/test/apps/instrumentation/src/env.js
new file mode 100644
index 000000000000..301360a310b3
--- /dev/null
+++ b/packages/adapter-node/test/apps/instrumentation/src/env.js
@@ -0,0 +1,5 @@
+import { defineEnvVars } from '@sveltejs/kit/env';
+
+export const variables = defineEnvVars({
+ MY_BASE_URL: { public: false, static: false }
+});
diff --git a/packages/adapter-node/test/apps/instrumentation/src/instrumentation.server.js b/packages/adapter-node/test/apps/instrumentation/src/instrumentation.server.js
new file mode 100644
index 000000000000..23bb7c6f0195
--- /dev/null
+++ b/packages/adapter-node/test/apps/instrumentation/src/instrumentation.server.js
@@ -0,0 +1,4 @@
+// The mere presence of this file makes adapter-node call `builder.instrument()`
+// (with `env: 'process.env'` on the eager-env branch), generating the env-init
+// facade — the code path under test.
+console.log('[instrumentation] evaluated');
diff --git a/packages/adapter-node/test/apps/instrumentation/src/lib/server/api-client.js b/packages/adapter-node/test/apps/instrumentation/src/lib/server/api-client.js
new file mode 100644
index 000000000000..9df3c06c4af0
--- /dev/null
+++ b/packages/adapter-node/test/apps/instrumentation/src/lib/server/api-client.js
@@ -0,0 +1,5 @@
+import { MY_BASE_URL } from '$app/env/private';
+
+// Module-scope read — mirrors configuring an API client with `baseUrl` from dynamic
+// env. Correct only if env is populated before this module evaluates.
+export const CAPTURED_AT_MODULE_SCOPE = MY_BASE_URL;
diff --git a/packages/adapter-node/test/apps/instrumentation/src/routes/+page.svelte b/packages/adapter-node/test/apps/instrumentation/src/routes/+page.svelte
new file mode 100644
index 000000000000..c45ce6b6da49
--- /dev/null
+++ b/packages/adapter-node/test/apps/instrumentation/src/routes/+page.svelte
@@ -0,0 +1 @@
+adapter-node instrumentation test app
diff --git a/packages/adapter-node/test/apps/instrumentation/src/routes/env/+server.js b/packages/adapter-node/test/apps/instrumentation/src/routes/env/+server.js
new file mode 100644
index 000000000000..ce060e8890dc
--- /dev/null
+++ b/packages/adapter-node/test/apps/instrumentation/src/routes/env/+server.js
@@ -0,0 +1,10 @@
+import process from 'node:process';
+import { json } from '@sveltejs/kit';
+import { CAPTURED_AT_MODULE_SCOPE } from '#lib/server/api-client.js';
+
+export function GET() {
+ return json({
+ captured: CAPTURED_AT_MODULE_SCOPE ?? null,
+ live: process.env.MY_BASE_URL ?? null
+ });
+}
diff --git a/packages/adapter-node/test/apps/instrumentation/test/boot.js b/packages/adapter-node/test/apps/instrumentation/test/boot.js
new file mode 100644
index 000000000000..ae502f7ae841
--- /dev/null
+++ b/packages/adapter-node/test/apps/instrumentation/test/boot.js
@@ -0,0 +1,66 @@
+// Boots the adapter-node build output with an instrumentation file present and
+// asserts that (1) the server starts at all — regression for the env-init facade
+// resolving `set_env` from the adapter's own `env.js` (sveltejs/kit#16303),
+// and (2) a module-scope `$app/env/private` read observes the runtime value —
+// regression for sveltejs/kit#16288.
+import { spawn } from 'node:child_process';
+import process from 'node:process';
+
+const PORT = 3399;
+const EXPECTED = 'https://api.example';
+
+const server = spawn(process.execPath, ['build/index.js'], {
+ env: {
+ ...process.env,
+ MY_BASE_URL: EXPECTED,
+ PORT: String(PORT),
+ HOST: '127.0.0.1'
+ },
+ stdio: ['ignore', 'pipe', 'pipe']
+});
+
+let output = '';
+server.stdout.on('data', (chunk) => (output += chunk));
+server.stderr.on('data', (chunk) => (output += chunk));
+
+let exited = false;
+server.on('exit', () => (exited = true));
+
+function fail(message) {
+ console.error(`FAIL: ${message}`);
+ console.error('--- server output ---');
+ console.error(output || '(none)');
+ server.kill();
+ process.exit(1);
+}
+
+try {
+ let response;
+ for (let attempt = 0; attempt < 40; attempt++) {
+ if (exited) fail('server exited before responding (startup crash)');
+ await new Promise((resolve) => setTimeout(resolve, 250));
+ try {
+ response = await fetch(`http://127.0.0.1:${PORT}/env`);
+ break;
+ } catch {
+ // not up yet
+ }
+ }
+ if (!response) fail('server never came up');
+
+ const { captured, live } = await response.json();
+ if (live !== EXPECTED)
+ fail(`live env read is ${JSON.stringify(live)}, expected ${JSON.stringify(EXPECTED)}`);
+ if (captured !== EXPECTED) {
+ fail(
+ `module-scope $app/env/private read captured ${JSON.stringify(captured)} — evaluated before env was set`
+ );
+ }
+ console.log(
+ 'PASS: server booted with instrumentation and module-scope env read observed the runtime value'
+ );
+ server.kill();
+ process.exit(0);
+} catch (error) {
+ fail(error.message);
+}
diff --git a/packages/adapter-node/test/apps/instrumentation/tsconfig.json b/packages/adapter-node/test/apps/instrumentation/tsconfig.json
new file mode 100644
index 000000000000..c2db702f9bb1
--- /dev/null
+++ b/packages/adapter-node/test/apps/instrumentation/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "allowJs": true,
+ "checkJs": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "moduleResolution": "bundler"
+ },
+ "extends": "./.svelte-kit/tsconfig.json"
+}
diff --git a/packages/adapter-node/test/apps/instrumentation/vite.config.js b/packages/adapter-node/test/apps/instrumentation/vite.config.js
new file mode 100644
index 000000000000..876e6414dc30
--- /dev/null
+++ b/packages/adapter-node/test/apps/instrumentation/vite.config.js
@@ -0,0 +1,14 @@
+import { sveltekit } from '@sveltejs/kit/vite';
+import adapter from '../../../index.js';
+
+/** @type {import('vite').UserConfig} */
+export default {
+ build: {
+ minify: false
+ },
+ plugins: [
+ sveltekit({
+ adapter: adapter()
+ })
+ ]
+};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0d296f27ddb2..87e56bebed43 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -351,6 +351,21 @@ importers:
specifier: 'catalog:'
version: 8.1.5(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0)
+ packages/adapter-node/test/apps/instrumentation:
+ devDependencies:
+ '@sveltejs/kit':
+ specifier: workspace:^
+ version: link:../../../../kit
+ '@sveltejs/vite-plugin-svelte':
+ specifier: 'catalog:'
+ version: 7.0.0(svelte@5.56.3(@typescript-eslint/types@8.61.1))(vite@8.1.5(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0))
+ svelte:
+ specifier: 'catalog:'
+ version: 5.56.3(@typescript-eslint/types@8.61.1)
+ vite:
+ specifier: 'catalog:'
+ version: 8.1.5(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0)
+
packages/adapter-static:
devDependencies:
'@playwright/test':