diff --git a/electron/gateway/config-sync-env.ts b/electron/gateway/config-sync-env.ts new file mode 100644 index 00000000..24944b23 --- /dev/null +++ b/electron/gateway/config-sync-env.ts @@ -0,0 +1,22 @@ +export const SUPERVISED_SYSTEMD_ENV_KEYS = [ + 'OPENCLAW_SYSTEMD_UNIT', + 'INVOCATION_ID', + 'SYSTEMD_EXEC_PID', + 'JOURNAL_STREAM', +] as const; + +export type GatewayEnv = Record; + +/** + * OpenClaw CLI treats certain environment variables as systemd supervisor hints. + * When present in ClawX-owned child-process launches, it can mistakenly enter + * a supervised process retry loop. Strip those variables so startup follows + * ClawX lifecycle. + */ +export function stripSystemdSupervisorEnv(env: GatewayEnv): GatewayEnv { + const next = { ...env }; + for (const key of SUPERVISED_SYSTEMD_ENV_KEYS) { + delete next[key]; + } + return next; +} diff --git a/electron/gateway/config-sync.ts b/electron/gateway/config-sync.ts index 2abdde4f..93209542 100644 --- a/electron/gateway/config-sync.ts +++ b/electron/gateway/config-sync.ts @@ -27,6 +27,8 @@ import { syncProxyConfigToOpenClaw } from '../utils/openclaw-proxy'; import { logger } from '../utils/logger'; import { prependPathEntry } from '../utils/env-path'; import { copyPluginFromNodeModules, fixupPluginManifest, cpSyncSafe } from '../utils/plugin-install'; +import { stripSystemdSupervisorEnv } from './config-sync-env'; + export interface GatewayLaunchContext { appSettings: Awaited>; @@ -317,7 +319,7 @@ export async function prepareGatewayLaunchContext(port: number): Promise = { - ...baseEnvPatched, + ...stripSystemdSupervisorEnv(baseEnvPatched), ...providerEnv, ...uvEnv, ...proxyEnv, diff --git a/tests/unit/config-sync.test.ts b/tests/unit/config-sync.test.ts new file mode 100644 index 00000000..143dbaa9 --- /dev/null +++ b/tests/unit/config-sync.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from 'vitest'; +import { stripSystemdSupervisorEnv } from '@electron/gateway/config-sync-env'; + +describe('stripSystemdSupervisorEnv', () => { + it('removes systemd supervisor marker env vars', () => { + const env = { + PATH: '/usr/bin:/bin', + OPENCLAW_SYSTEMD_UNIT: 'openclaw-gateway.service', + INVOCATION_ID: 'abc123', + SYSTEMD_EXEC_PID: '777', + JOURNAL_STREAM: '8:12345', + OTHER: 'keep-me', + }; + + const result = stripSystemdSupervisorEnv(env); + + expect(result).toEqual({ + PATH: '/usr/bin:/bin', + OTHER: 'keep-me', + }); + }); + + it('keeps unrelated variables unchanged', () => { + const env = { + NODE_ENV: 'production', + OPENCLAW_GATEWAY_TOKEN: 'token', + CLAWDBOT_SKIP_CHANNELS: '0', + }; + + expect(stripSystemdSupervisorEnv(env)).toEqual(env); + }); + + it('does not mutate source env object', () => { + const env = { + OPENCLAW_SYSTEMD_UNIT: 'openclaw-gateway.service', + VALUE: '1', + }; + const before = { ...env }; + + const result = stripSystemdSupervisorEnv(env); + + expect(env).toEqual(before); + expect(result).toEqual({ VALUE: '1' }); + }); +});