From 315aac0f0455b835cf12b9a82031b58ea62856de Mon Sep 17 00:00:00 2001 From: Wujie Li <66515269+wujieli0207@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:59:39 +0800 Subject: [PATCH] fix: sanitize __NEXT_PRIVATE_* env vars from child processes (#161) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: preserve paragraph breaks between text blocks in bridge responses When Claude's response includes tool calls (Read, Edit, Bash, etc.), the response text is split into multiple content blocks — one before each tool call and one after. These blocks were joined with an empty string (.join('')), causing all paragraphs to concatenate without any spacing in the final Telegram message. Changed .join('') to .join('\n\n') so paragraph breaks between text blocks are preserved, matching how they appear in the desktop UI. * fix: sanitize __NEXT_PRIVATE_* env vars from child processes The bundled Next.js standalone server sets __NEXT_PRIVATE_STANDALONE_CONFIG and __NEXT_PRIVATE_ORIGIN in process.env at runtime. These leak into every child process spawned by the Electron main process (Claude Code CLI, node version checks, package installs, etc.), which in turn pollutes tmux/terminal sessions. When other Next.js projects detect __NEXT_PRIVATE_STANDALONE_CONFIG in their environment, they skip normal next.config loading and use CodePilot's baked-in config instead — silently breaking builds and dev servers system-wide. Fix: strip all __NEXT_PRIVATE_* variables when constructing env objects for child processes. The standalone server.js already sets these internally, so they don't need to be passed from the parent. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude Co-Authored-By: Happy --------- Co-authored-by: Claude Co-authored-by: Happy --- electron/main.ts | 25 ++++++++++++++++++++++--- src/lib/bridge/conversation-engine.ts | 4 ++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index ba5ed4d..9afcbd0 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -5,6 +5,25 @@ import fs from 'fs'; import net from 'net'; import os from 'os'; +/** + * Return a copy of process.env without __NEXT_PRIVATE_* variables. + * + * The bundled Next.js standalone server sets these at runtime + * (e.g. __NEXT_PRIVATE_STANDALONE_CONFIG, __NEXT_PRIVATE_ORIGIN). + * If they leak into child-process environments they cause every + * other Next.js project on the machine to skip its own config + * loading, breaking builds and dev servers. + */ +function sanitizedProcessEnv(): Record { + const env: Record = {}; + for (const [key, value] of Object.entries(process.env)) { + if (!key.startsWith('__NEXT_PRIVATE_') && value !== undefined) { + env[key] = value; + } + } + return env; +} + let mainWindow: BrowserWindow | null = null; let serverProcess: Electron.UtilityProcess | null = null; let serverPort: number | null = null; @@ -388,7 +407,7 @@ function startServer(port: number): Electron.UtilityProcess { const env: Record = { ...userShellEnv, - ...(process.env as Record), + ...sanitizedProcessEnv(), // Ensure user shell env vars override (especially API keys) ...userShellEnv, PORT: String(port), @@ -551,7 +570,7 @@ app.whenReady().then(async () => { ipcMain.handle('install:check-prerequisites', async () => { const expandedPath = getExpandedShellPath(); - const execEnv = { ...process.env, ...userShellEnv, PATH: expandedPath }; + const execEnv = { ...sanitizedProcessEnv(), ...userShellEnv, PATH: expandedPath }; const execOpts = { timeout: 5000, encoding: 'utf-8' as const, env: execEnv }; let hasNode = false; @@ -615,7 +634,7 @@ app.whenReady().then(async () => { const expandedPath = getExpandedShellPath(); const execEnv: Record = { ...userShellEnv, - ...(process.env as Record), + ...sanitizedProcessEnv(), ...userShellEnv, PATH: expandedPath, }; diff --git a/src/lib/bridge/conversation-engine.ts b/src/lib/bridge/conversation-engine.ts index c213fd5..e097988 100644 --- a/src/lib/bridge/conversation-engine.ts +++ b/src/lib/bridge/conversation-engine.ts @@ -363,7 +363,7 @@ async function consumeStream( : contentBlocks .filter((b): b is Extract => b.type === 'text') .map((b) => b.text) - .join('') + .join('\n\n') .trim(); if (content) { @@ -400,7 +400,7 @@ async function consumeStream( : contentBlocks .filter((b): b is Extract => b.type === 'text') .map((b) => b.text) - .join('') + .join('\n\n') .trim(); if (content) { addMessage(sessionId, 'assistant', content);