fix(win): prevent user PATH clobbering and normalize gateway PATH env (#459)
This commit is contained in:
@@ -11,6 +11,7 @@ import { syncGatewayTokenToConfig, syncBrowserConfigToOpenClaw, sanitizeOpenClaw
|
||||
import { buildProxyEnv, resolveProxySettings } from '../utils/proxy';
|
||||
import { syncProxyConfigToOpenClaw } from '../utils/openclaw-proxy';
|
||||
import { logger } from '../utils/logger';
|
||||
import { prependPathEntry } from '../utils/env-path';
|
||||
|
||||
export interface GatewayLaunchContext {
|
||||
appSettings: Awaited<ReturnType<typeof getAllSettings>>;
|
||||
@@ -141,9 +142,6 @@ export async function prepareGatewayLaunchContext(port: number): Promise<Gateway
|
||||
? path.join(process.resourcesPath, 'bin')
|
||||
: path.join(process.cwd(), 'resources', 'bin', target);
|
||||
const binPathExists = existsSync(binPath);
|
||||
const finalPath = binPathExists
|
||||
? `${binPath}${path.delimiter}${process.env.PATH || ''}`
|
||||
: process.env.PATH || '';
|
||||
|
||||
const { providerEnv, loadedProviderKeyCount } = await loadProviderEnv();
|
||||
const { skipChannels, channelStartupSummary } = await resolveChannelStartupPolicy();
|
||||
@@ -155,9 +153,12 @@ export async function prepareGatewayLaunchContext(port: number): Promise<Gateway
|
||||
: 'disabled';
|
||||
|
||||
const { NODE_OPTIONS: _nodeOptions, ...baseEnv } = process.env;
|
||||
const baseEnvRecord = baseEnv as Record<string, string | undefined>;
|
||||
const baseEnvPatched = binPathExists
|
||||
? prependPathEntry(baseEnvRecord, binPath).env
|
||||
: baseEnvRecord;
|
||||
const forkEnv: Record<string, string | undefined> = {
|
||||
...baseEnv,
|
||||
PATH: finalPath,
|
||||
...baseEnvPatched,
|
||||
...providerEnv,
|
||||
...uvEnv,
|
||||
...proxyEnv,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getOpenClawDir, getOpenClawEntryPath } from '../utils/paths';
|
||||
import { getUvMirrorEnv } from '../utils/uv-env';
|
||||
import { isPythonReady, setupManagedPython } from '../utils/uv-setup';
|
||||
import { logger } from '../utils/logger';
|
||||
import { prependPathEntry } from '../utils/env-path';
|
||||
|
||||
export function warmupManagedPythonReadiness(): void {
|
||||
void isPythonReady().then((pythonReady) => {
|
||||
@@ -269,9 +270,10 @@ export async function runOpenClawDoctorRepair(): Promise<boolean> {
|
||||
? path.join(process.resourcesPath, 'bin')
|
||||
: path.join(process.cwd(), 'resources', 'bin', target);
|
||||
const binPathExists = existsSync(binPath);
|
||||
const finalPath = binPathExists
|
||||
? `${binPath}${path.delimiter}${process.env.PATH || ''}`
|
||||
: process.env.PATH || '';
|
||||
const baseProcessEnv = process.env as Record<string, string | undefined>;
|
||||
const baseEnvPatched = binPathExists
|
||||
? prependPathEntry(baseProcessEnv, binPath).env
|
||||
: baseProcessEnv;
|
||||
|
||||
const uvEnv = await getUvMirrorEnv();
|
||||
const doctorArgs = ['doctor', '--fix', '--yes', '--non-interactive'];
|
||||
@@ -281,8 +283,7 @@ export async function runOpenClawDoctorRepair(): Promise<boolean> {
|
||||
|
||||
return await new Promise<boolean>((resolve) => {
|
||||
const forkEnv: Record<string, string | undefined> = {
|
||||
...process.env,
|
||||
PATH: finalPath,
|
||||
...baseEnvPatched,
|
||||
...uvEnv,
|
||||
OPENCLAW_NO_RESPAWN: '1',
|
||||
};
|
||||
|
||||
59
electron/utils/env-path.ts
Normal file
59
electron/utils/env-path.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
type EnvMap = Record<string, string | undefined>;
|
||||
|
||||
function isPathKey(key: string): boolean {
|
||||
return key.toLowerCase() === 'path';
|
||||
}
|
||||
|
||||
function preferredPathKey(): string {
|
||||
return process.platform === 'win32' ? 'Path' : 'PATH';
|
||||
}
|
||||
|
||||
function pathDelimiter(): string {
|
||||
return process.platform === 'win32' ? ';' : ':';
|
||||
}
|
||||
|
||||
export function getPathEnvKey(env: EnvMap): string {
|
||||
const keys = Object.keys(env).filter(isPathKey);
|
||||
if (keys.length === 0) return preferredPathKey();
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
if (keys.includes('Path')) return 'Path';
|
||||
if (keys.includes('PATH')) return 'PATH';
|
||||
return keys[0];
|
||||
}
|
||||
|
||||
if (keys.includes('PATH')) return 'PATH';
|
||||
return keys[0];
|
||||
}
|
||||
|
||||
export function getPathEnvValue(env: EnvMap): string {
|
||||
const key = getPathEnvKey(env);
|
||||
return env[key] ?? '';
|
||||
}
|
||||
|
||||
export function setPathEnvValue(
|
||||
env: EnvMap,
|
||||
nextPath: string,
|
||||
): EnvMap {
|
||||
const nextEnv: EnvMap = { ...env };
|
||||
for (const key of Object.keys(nextEnv)) {
|
||||
if (isPathKey(key)) {
|
||||
delete nextEnv[key];
|
||||
}
|
||||
}
|
||||
|
||||
nextEnv[getPathEnvKey(env)] = nextPath;
|
||||
return nextEnv;
|
||||
}
|
||||
|
||||
export function prependPathEntry(
|
||||
env: EnvMap,
|
||||
entry: string,
|
||||
): { env: EnvMap; path: string } {
|
||||
const current = getPathEnvValue(env);
|
||||
const nextPath = current ? `${entry}${pathDelimiter()}${current}` : entry;
|
||||
return {
|
||||
env: setPathEnvValue(env, nextPath),
|
||||
path: nextPath,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user