From de0e5af9422fb05faf083c7942a88d90227dd9ab Mon Sep 17 00:00:00 2001 From: GodsBoy Date: Tue, 17 Mar 2026 17:13:03 +0200 Subject: [PATCH 1/5] fix(install): auto-set NODE_EXTRA_CA_CERTS for nvm Node.js on Linux --- docs/help/environment.md | 19 ++++++++++ src/cli/daemon-cli/install.ts | 50 ++++++++++++++++++++++++ src/daemon/service-env.test.ts | 69 ++++++++++++++++++++++++++++++++++ src/daemon/service-env.ts | 31 ++++++++++++++- 4 files changed, 168 insertions(+), 1 deletion(-) diff --git a/docs/help/environment.md b/docs/help/environment.md index 45faad7c66c..bf531f23f67 100644 --- a/docs/help/environment.md +++ b/docs/help/environment.md @@ -133,6 +133,25 @@ When set, `OPENCLAW_HOME` replaces the system home directory (`$HOME` / `os.home `OPENCLAW_HOME` can also be set to a tilde path (e.g. `~/svc`), which gets expanded using `$HOME` before use. +## nvm users: web_fetch TLS failures + +If Node.js was installed via **nvm** (not the system package manager), the built-in `fetch()` uses +nvm's bundled CA store, which may be missing modern root CAs (ISRG Root X1/X2 for Let's Encrypt, +DigiCert Global Root G2, etc.). This causes `web_fetch` to fail with `"fetch failed"` on most HTTPS sites. + +Since `v2026.3.17`, `openclaw gateway install` on Linux automatically detects nvm and writes the +fix to both the systemd service environment and `~/.openclaw/.env`. + +**Manual fix (for older versions or manual gateway starts):** + +Add to `~/.openclaw/.env`: + +``` +NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt +``` + +Then restart the gateway. This appends the system CA bundle to Node's bundled store. + ## Related - [Gateway configuration](/gateway/configuration) diff --git a/src/cli/daemon-cli/install.ts b/src/cli/daemon-cli/install.ts index 023ea5e520e..7d7c06d01e3 100644 --- a/src/cli/daemon-cli/install.ts +++ b/src/cli/daemon-cli/install.ts @@ -1,3 +1,5 @@ +import fs from "node:fs"; +import path from "node:path"; import { buildGatewayInstallPlan } from "../../commands/daemon-install-helpers.js"; import { DEFAULT_GATEWAY_DAEMON_RUNTIME, @@ -5,6 +7,8 @@ import { } from "../../commands/daemon-runtime.js"; import { resolveGatewayInstallToken } from "../../commands/gateway-install-token.js"; import { readBestEffortConfig, resolveGatewayPort } from "../../config/config.js"; +import { resolveGatewayStateDir } from "../../daemon/paths.js"; +import { isNvmNode } from "../../daemon/service-env.js"; import { resolveGatewayService } from "../../daemon/service.js"; import { isNonFatalSystemdInstallProbeError } from "../../daemon/systemd.js"; import { defaultRuntime } from "../../runtime.js"; @@ -119,4 +123,50 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) { }); }, }); + + // On Linux with nvm-installed Node, ensure NODE_EXTRA_CA_CERTS is in ~/.openclaw/.env + // so manual `openclaw gateway run` also picks up the system CA bundle via dotenv. + ensureNvmCaCertsInDotEnv({ env: process.env, json, warnings }); +} + +/** + * When Node.js is installed via nvm on Linux, write NODE_EXTRA_CA_CERTS to + * the global .env file so non-service runs (e.g. `openclaw gateway run`) + * also get the system CA bundle. The service environment already handles this + * via buildServiceEnvironment, but dotenv covers the manual-start path. + */ +function ensureNvmCaCertsInDotEnv(params: { + env: Record; + json: boolean; + warnings: string[]; +}): void { + if (process.platform !== "linux" || !isNvmNode(params.env, process.execPath)) { + return; + } + if (params.env.NODE_EXTRA_CA_CERTS) { + return; + } + + try { + const stateDir = resolveGatewayStateDir(params.env); + const envFile = path.join(stateDir, ".env"); + const existing = fs.existsSync(envFile) ? fs.readFileSync(envFile, "utf8") : ""; + if (existing.includes("NODE_EXTRA_CA_CERTS")) { + return; + } + const line = "NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt\n"; + const content = existing.endsWith("\n") || !existing ? existing + line : `${existing}\n${line}`; + fs.mkdirSync(stateDir, { recursive: true }); + fs.writeFileSync(envFile, content, "utf8"); + + const message = + "nvm detected: wrote NODE_EXTRA_CA_CERTS to ~/.openclaw/.env for TLS compatibility"; + if (params.json) { + params.warnings.push(message); + } else { + defaultRuntime.log(message); + } + } catch { + // Best-effort; the service environment already has the var via buildServiceEnvironment. + } } diff --git a/src/daemon/service-env.test.ts b/src/daemon/service-env.test.ts index f8297a28554..89753dd99eb 100644 --- a/src/daemon/service-env.test.ts +++ b/src/daemon/service-env.test.ts @@ -8,6 +8,7 @@ import { buildServiceEnvironment, getMinimalServicePathParts, getMinimalServicePathPartsFromEnv, + isNvmNode, } from "./service-env.js"; describe("getMinimalServicePathParts - Linux user directories", () => { @@ -500,6 +501,74 @@ describe("shared Node TLS env defaults", () => { }); }); +describe("isNvmNode", () => { + it("returns true when NVM_DIR env var is set", () => { + expect(isNvmNode({ NVM_DIR: "/home/user/.nvm" })).toBe(true); + }); + + it("returns true when execPath contains /.nvm/", () => { + expect(isNvmNode({}, "/home/user/.nvm/versions/node/v22.22.0/bin/node")).toBe(true); + }); + + it("returns false when neither NVM_DIR nor nvm execPath", () => { + expect(isNvmNode({}, "/usr/bin/node")).toBe(false); + }); + + it("returns false for empty env and system execPath", () => { + expect(isNvmNode({}, "/usr/local/bin/node")).toBe(false); + }); + + it("returns true when NVM_DIR is set even with system execPath", () => { + expect(isNvmNode({ NVM_DIR: "/home/user/.nvm" }, "/usr/bin/node")).toBe(true); + }); +}); + +describe("shared Node TLS env — Linux nvm detection", () => { + const builders = [ + { + name: "gateway service env", + build: (env: Record, platform?: NodeJS.Platform) => + buildServiceEnvironment({ env, port: 18789, platform }), + }, + { + name: "node service env", + build: (env: Record, platform?: NodeJS.Platform) => + buildNodeServiceEnvironment({ env, platform }), + }, + ] as const; + + it.each(builders)( + "$name defaults NODE_EXTRA_CA_CERTS on Linux when NVM_DIR is set", + ({ build }) => { + const env = build({ HOME: "/home/user", NVM_DIR: "/home/user/.nvm" }, "linux"); + expect(env.NODE_EXTRA_CA_CERTS).toBe("/etc/ssl/certs/ca-certificates.crt"); + }, + ); + + it.each(builders)( + "$name does not default NODE_EXTRA_CA_CERTS on Linux without nvm", + ({ build }) => { + const env = build({ HOME: "/home/user" }, "linux"); + expect(env.NODE_EXTRA_CA_CERTS).toBeUndefined(); + }, + ); + + it.each(builders)( + "$name respects user-provided NODE_EXTRA_CA_CERTS on Linux with nvm", + ({ build }) => { + const env = build( + { + HOME: "/home/user", + NVM_DIR: "/home/user/.nvm", + NODE_EXTRA_CA_CERTS: "/custom/ca-bundle.crt", + }, + "linux", + ); + expect(env.NODE_EXTRA_CA_CERTS).toBe("/custom/ca-bundle.crt"); + }, + ); +}); + describe("resolveGatewayStateDir", () => { it("uses the default state dir when no overrides are set", () => { const env = { HOME: "/Users/test" }; diff --git a/src/daemon/service-env.ts b/src/daemon/service-env.ts index cb26c210efb..0d3f189f351 100644 --- a/src/daemon/service-env.ts +++ b/src/daemon/service-env.ts @@ -15,6 +15,28 @@ import { resolveNodeWindowsTaskName, } from "./constants.js"; +/** Standard system CA bundle path on Debian/Ubuntu/Alpine Linux. */ +const LINUX_SYSTEM_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"; + +/** + * Detect if Node.js was installed via nvm. + * nvm-installed Node uses a bundled CA certificate store that may be missing modern + * root CAs (ISRG Root X1/X2, DigiCert Global Root G2, etc.), causing TLS failures + * with Node's built-in fetch (undici) for the majority of real-world HTTPS sites. + * + * Pass `execPath` explicitly to also check the binary path (e.g. `process.execPath`). + * Without it, only the `NVM_DIR` env var is checked. + */ +export function isNvmNode(env?: Record, execPath?: string): boolean { + if (env?.NVM_DIR) { + return true; + } + if (execPath !== undefined) { + return execPath.includes("/.nvm/"); + } + return false; +} + export type MinimalServicePathOptions = { platform?: NodeJS.Platform; extraDirs?: string[]; @@ -325,8 +347,15 @@ function resolveSharedServiceEnvironmentFields( // On macOS, launchd services don't inherit the shell environment, so Node's undici/fetch // cannot locate the system CA bundle. Default to /etc/ssl/cert.pem so TLS verification // works correctly when running as a LaunchAgent without extra user configuration. + // On Linux, nvm-installed Node uses a bundled CA store that is missing modern root CAs. + // Default to the system CA bundle when nvm is detected so TLS works out of the box. const nodeCaCerts = - env.NODE_EXTRA_CA_CERTS ?? (platform === "darwin" ? "/etc/ssl/cert.pem" : undefined); + env.NODE_EXTRA_CA_CERTS ?? + (platform === "darwin" + ? "/etc/ssl/cert.pem" + : platform === "linux" && isNvmNode(env) + ? LINUX_SYSTEM_CA_BUNDLE + : undefined); const nodeUseSystemCa = env.NODE_USE_SYSTEM_CA ?? (platform === "darwin" ? "1" : undefined); return { stateDir, From ec459def0729c4cab2f8ffa99250309311472d40 Mon Sep 17 00:00:00 2001 From: GodsBoy Date: Tue, 17 Mar 2026 17:24:20 +0200 Subject: [PATCH 2/5] fix(install): probe CA bundle path across Linux distros, backfill on already-installed --- src/cli/daemon-cli/install.ts | 11 +++++++++-- src/daemon/service-env.test.ts | 17 ++++++++++++++++- src/daemon/service-env.ts | 29 +++++++++++++++++++++++++---- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/cli/daemon-cli/install.ts b/src/cli/daemon-cli/install.ts index 7d7c06d01e3..8663133f053 100644 --- a/src/cli/daemon-cli/install.ts +++ b/src/cli/daemon-cli/install.ts @@ -8,7 +8,7 @@ import { import { resolveGatewayInstallToken } from "../../commands/gateway-install-token.js"; import { readBestEffortConfig, resolveGatewayPort } from "../../config/config.js"; import { resolveGatewayStateDir } from "../../daemon/paths.js"; -import { isNvmNode } from "../../daemon/service-env.js"; +import { isNvmNode, resolveLinuxSystemCaBundle } from "../../daemon/service-env.js"; import { resolveGatewayService } from "../../daemon/service.js"; import { isNonFatalSystemdInstallProbeError } from "../../daemon/systemd.js"; import { defaultRuntime } from "../../runtime.js"; @@ -58,6 +58,9 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) { } if (loaded) { if (!opts.force) { + // Backfill NODE_EXTRA_CA_CERTS even when service is already installed, + // so users upgrading to this fix don't need --force to get the .env entry. + ensureNvmCaCertsInDotEnv({ env: process.env, json, warnings }); emit({ ok: true, result: "already-installed", @@ -148,13 +151,17 @@ function ensureNvmCaCertsInDotEnv(params: { } try { + const caBundle = resolveLinuxSystemCaBundle(); + if (!caBundle) { + return; + } const stateDir = resolveGatewayStateDir(params.env); const envFile = path.join(stateDir, ".env"); const existing = fs.existsSync(envFile) ? fs.readFileSync(envFile, "utf8") : ""; if (existing.includes("NODE_EXTRA_CA_CERTS")) { return; } - const line = "NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt\n"; + const line = `NODE_EXTRA_CA_CERTS=${caBundle}\n`; const content = existing.endsWith("\n") || !existing ? existing + line : `${existing}\n${line}`; fs.mkdirSync(stateDir, { recursive: true }); fs.writeFileSync(envFile, content, "utf8"); diff --git a/src/daemon/service-env.test.ts b/src/daemon/service-env.test.ts index 89753dd99eb..4e12400d261 100644 --- a/src/daemon/service-env.test.ts +++ b/src/daemon/service-env.test.ts @@ -9,6 +9,7 @@ import { getMinimalServicePathParts, getMinimalServicePathPartsFromEnv, isNvmNode, + resolveLinuxSystemCaBundle, } from "./service-env.js"; describe("getMinimalServicePathParts - Linux user directories", () => { @@ -523,6 +524,17 @@ describe("isNvmNode", () => { }); }); +describe("resolveLinuxSystemCaBundle", () => { + it("returns a known CA bundle path when one exists", () => { + const result = resolveLinuxSystemCaBundle(); + if (process.platform === "linux") { + // On a real Linux host, at least one standard CA bundle should exist. + expect(result).toMatch(/\.(crt|pem)$/); + } + // On non-Linux CI or minimal containers, result may be undefined. + }); +}); + describe("shared Node TLS env — Linux nvm detection", () => { const builders = [ { @@ -537,11 +549,14 @@ describe("shared Node TLS env — Linux nvm detection", () => { }, ] as const; + // The expected CA path depends on what the host actually has on disk. + const expectedCaBundle = resolveLinuxSystemCaBundle(); + it.each(builders)( "$name defaults NODE_EXTRA_CA_CERTS on Linux when NVM_DIR is set", ({ build }) => { const env = build({ HOME: "/home/user", NVM_DIR: "/home/user/.nvm" }, "linux"); - expect(env.NODE_EXTRA_CA_CERTS).toBe("/etc/ssl/certs/ca-certificates.crt"); + expect(env.NODE_EXTRA_CA_CERTS).toBe(expectedCaBundle); }, ); diff --git a/src/daemon/service-env.ts b/src/daemon/service-env.ts index 0d3f189f351..d9f0331ac4d 100644 --- a/src/daemon/service-env.ts +++ b/src/daemon/service-env.ts @@ -1,3 +1,4 @@ +import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { VERSION } from "../version.js"; @@ -15,8 +16,28 @@ import { resolveNodeWindowsTaskName, } from "./constants.js"; -/** Standard system CA bundle path on Debian/Ubuntu/Alpine Linux. */ -const LINUX_SYSTEM_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"; +/** Known system CA bundle paths across common Linux distros. */ +const LINUX_CA_BUNDLE_PATHS = [ + "/etc/ssl/certs/ca-certificates.crt", // Debian, Ubuntu, Alpine + "/etc/pki/tls/certs/ca-bundle.crt", // RHEL, Fedora, CentOS + "/etc/ssl/ca-bundle.pem", // openSUSE +] as const; + +/** + * Find the system CA bundle on this Linux host. + * Returns the first existing path, or `undefined` if none is found. + */ +export function resolveLinuxSystemCaBundle(): string | undefined { + for (const candidate of LINUX_CA_BUNDLE_PATHS) { + try { + fs.accessSync(candidate, fs.constants.R_OK); + return candidate; + } catch { + continue; + } + } + return undefined; +} /** * Detect if Node.js was installed via nvm. @@ -348,13 +369,13 @@ function resolveSharedServiceEnvironmentFields( // cannot locate the system CA bundle. Default to /etc/ssl/cert.pem so TLS verification // works correctly when running as a LaunchAgent without extra user configuration. // On Linux, nvm-installed Node uses a bundled CA store that is missing modern root CAs. - // Default to the system CA bundle when nvm is detected so TLS works out of the box. + // Default to the system CA bundle when nvm is detected and the bundle exists on disk. const nodeCaCerts = env.NODE_EXTRA_CA_CERTS ?? (platform === "darwin" ? "/etc/ssl/cert.pem" : platform === "linux" && isNvmNode(env) - ? LINUX_SYSTEM_CA_BUNDLE + ? resolveLinuxSystemCaBundle() : undefined); const nodeUseSystemCa = env.NODE_USE_SYSTEM_CA ?? (platform === "darwin" ? "1" : undefined); return { From 4c8fd516649b480a13b3955588b61e4aa11cae17 Mon Sep 17 00:00:00 2001 From: GodsBoy Date: Tue, 17 Mar 2026 17:35:46 +0200 Subject: [PATCH 3/5] fix(install): inject execPath for nvm detection in service env builder --- src/daemon/service-env.test.ts | 40 +++++++++++++++++++++++++++------- src/daemon/service-env.ts | 11 +++++++--- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/daemon/service-env.test.ts b/src/daemon/service-env.test.ts index 4e12400d261..60fcc4d4fb1 100644 --- a/src/daemon/service-env.test.ts +++ b/src/daemon/service-env.test.ts @@ -458,16 +458,18 @@ describe("buildNodeServiceEnvironment", () => { }); describe("shared Node TLS env defaults", () => { + // Pass an explicit non-nvm execPath so tests are deterministic regardless of + // whether the test runner itself runs under nvm. const builders = [ { name: "gateway service env", build: (env: Record, platform?: NodeJS.Platform) => - buildServiceEnvironment({ env, port: 18789, platform }), + buildServiceEnvironment({ env, port: 18789, platform, execPath: "/usr/bin/node" }), }, { name: "node service env", build: (env: Record, platform?: NodeJS.Platform) => - buildNodeServiceEnvironment({ env, platform }), + buildNodeServiceEnvironment({ env, platform, execPath: "/usr/bin/node" }), }, ] as const; @@ -536,16 +538,25 @@ describe("resolveLinuxSystemCaBundle", () => { }); describe("shared Node TLS env — Linux nvm detection", () => { + const nvmExecPath = "/home/user/.nvm/versions/node/v22.22.0/bin/node"; + const nonNvmExecPath = "/usr/bin/node"; + const builders = [ { name: "gateway service env", - build: (env: Record, platform?: NodeJS.Platform) => - buildServiceEnvironment({ env, port: 18789, platform }), + build: ( + env: Record, + platform?: NodeJS.Platform, + execPath?: string, + ) => buildServiceEnvironment({ env, port: 18789, platform, execPath }), }, { name: "node service env", - build: (env: Record, platform?: NodeJS.Platform) => - buildNodeServiceEnvironment({ env, platform }), + build: ( + env: Record, + platform?: NodeJS.Platform, + execPath?: string, + ) => buildNodeServiceEnvironment({ env, platform, execPath }), }, ] as const; @@ -555,7 +566,19 @@ describe("shared Node TLS env — Linux nvm detection", () => { it.each(builders)( "$name defaults NODE_EXTRA_CA_CERTS on Linux when NVM_DIR is set", ({ build }) => { - const env = build({ HOME: "/home/user", NVM_DIR: "/home/user/.nvm" }, "linux"); + const env = build( + { HOME: "/home/user", NVM_DIR: "/home/user/.nvm" }, + "linux", + nonNvmExecPath, + ); + expect(env.NODE_EXTRA_CA_CERTS).toBe(expectedCaBundle); + }, + ); + + it.each(builders)( + "$name defaults NODE_EXTRA_CA_CERTS on Linux when execPath is under nvm", + ({ build }) => { + const env = build({ HOME: "/home/user" }, "linux", nvmExecPath); expect(env.NODE_EXTRA_CA_CERTS).toBe(expectedCaBundle); }, ); @@ -563,7 +586,7 @@ describe("shared Node TLS env — Linux nvm detection", () => { it.each(builders)( "$name does not default NODE_EXTRA_CA_CERTS on Linux without nvm", ({ build }) => { - const env = build({ HOME: "/home/user" }, "linux"); + const env = build({ HOME: "/home/user" }, "linux", nonNvmExecPath); expect(env.NODE_EXTRA_CA_CERTS).toBeUndefined(); }, ); @@ -578,6 +601,7 @@ describe("shared Node TLS env — Linux nvm detection", () => { NODE_EXTRA_CA_CERTS: "/custom/ca-bundle.crt", }, "linux", + nvmExecPath, ); expect(env.NODE_EXTRA_CA_CERTS).toBe("/custom/ca-bundle.crt"); }, diff --git a/src/daemon/service-env.ts b/src/daemon/service-env.ts index d9f0331ac4d..98e2d4960ea 100644 --- a/src/daemon/service-env.ts +++ b/src/daemon/service-env.ts @@ -291,10 +291,12 @@ export function buildServiceEnvironment(params: { launchdLabel?: string; platform?: NodeJS.Platform; extraPathDirs?: string[]; + /** Override process.execPath for nvm detection (testing). */ + execPath?: string; }): Record { const { env, port, launchdLabel, extraPathDirs } = params; const platform = params.platform ?? process.platform; - const sharedEnv = resolveSharedServiceEnvironmentFields(env, platform, extraPathDirs); + const sharedEnv = resolveSharedServiceEnvironmentFields(env, platform, extraPathDirs, params.execPath); const profile = env.OPENCLAW_PROFILE; const resolvedLaunchdLabel = launchdLabel || (platform === "darwin" ? resolveGatewayLaunchAgentLabel(profile) : undefined); @@ -316,10 +318,12 @@ export function buildNodeServiceEnvironment(params: { env: Record; platform?: NodeJS.Platform; extraPathDirs?: string[]; + /** Override process.execPath for nvm detection (testing). */ + execPath?: string; }): Record { const { env, extraPathDirs } = params; const platform = params.platform ?? process.platform; - const sharedEnv = resolveSharedServiceEnvironmentFields(env, platform, extraPathDirs); + const sharedEnv = resolveSharedServiceEnvironmentFields(env, platform, extraPathDirs, params.execPath); const gatewayToken = env.OPENCLAW_GATEWAY_TOKEN?.trim() || env.CLAWDBOT_GATEWAY_TOKEN?.trim() || undefined; return { @@ -359,6 +363,7 @@ function resolveSharedServiceEnvironmentFields( env: Record, platform: NodeJS.Platform, extraPathDirs: string[] | undefined, + execPath?: string, ): SharedServiceEnvironmentFields { const stateDir = env.OPENCLAW_STATE_DIR; const configPath = env.OPENCLAW_CONFIG_PATH; @@ -374,7 +379,7 @@ function resolveSharedServiceEnvironmentFields( env.NODE_EXTRA_CA_CERTS ?? (platform === "darwin" ? "/etc/ssl/cert.pem" - : platform === "linux" && isNvmNode(env) + : platform === "linux" && isNvmNode(env, execPath ?? process.execPath) ? resolveLinuxSystemCaBundle() : undefined); const nodeUseSystemCa = env.NODE_USE_SYSTEM_CA ?? (platform === "darwin" ? "1" : undefined); From 7345a70f8664e4e7487244e1ae504c153ee26b10 Mon Sep 17 00:00:00 2001 From: GodsBoy Date: Fri, 20 Mar 2026 19:01:41 +0200 Subject: [PATCH 4/5] fix(install): address review feedback on ensureNvmCaCertsInDotEnv - Make platform/execPath injectable for testability (consistent with service-env builders) - Use regex anchor for NODE_EXTRA_CA_CERTS check so commented-out lines don't prevent writing the active entry --- src/cli/daemon-cli/install.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/cli/daemon-cli/install.ts b/src/cli/daemon-cli/install.ts index 8663133f053..7be6b882643 100644 --- a/src/cli/daemon-cli/install.ts +++ b/src/cli/daemon-cli/install.ts @@ -142,8 +142,12 @@ function ensureNvmCaCertsInDotEnv(params: { env: Record; json: boolean; warnings: string[]; + platform?: NodeJS.Platform; + execPath?: string; }): void { - if (process.platform !== "linux" || !isNvmNode(params.env, process.execPath)) { + const platform = params.platform ?? process.platform; + const execPath = params.execPath ?? process.execPath; + if (platform !== "linux" || !isNvmNode(params.env, execPath)) { return; } if (params.env.NODE_EXTRA_CA_CERTS) { @@ -158,7 +162,7 @@ function ensureNvmCaCertsInDotEnv(params: { const stateDir = resolveGatewayStateDir(params.env); const envFile = path.join(stateDir, ".env"); const existing = fs.existsSync(envFile) ? fs.readFileSync(envFile, "utf8") : ""; - if (existing.includes("NODE_EXTRA_CA_CERTS")) { + if (/^NODE_EXTRA_CA_CERTS=/m.test(existing)) { return; } const line = `NODE_EXTRA_CA_CERTS=${caBundle}\n`; From fb3b871e44f688ea9e17836ddf8f1f4fe5436d5d Mon Sep 17 00:00:00 2001 From: GodsBoy Date: Fri, 20 Mar 2026 19:15:52 +0200 Subject: [PATCH 5/5] style: format service-env.ts (oxfmt line-length wrapping) --- src/daemon/service-env.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/daemon/service-env.ts b/src/daemon/service-env.ts index 98e2d4960ea..26568bb183f 100644 --- a/src/daemon/service-env.ts +++ b/src/daemon/service-env.ts @@ -296,7 +296,12 @@ export function buildServiceEnvironment(params: { }): Record { const { env, port, launchdLabel, extraPathDirs } = params; const platform = params.platform ?? process.platform; - const sharedEnv = resolveSharedServiceEnvironmentFields(env, platform, extraPathDirs, params.execPath); + const sharedEnv = resolveSharedServiceEnvironmentFields( + env, + platform, + extraPathDirs, + params.execPath, + ); const profile = env.OPENCLAW_PROFILE; const resolvedLaunchdLabel = launchdLabel || (platform === "darwin" ? resolveGatewayLaunchAgentLabel(profile) : undefined); @@ -323,7 +328,12 @@ export function buildNodeServiceEnvironment(params: { }): Record { const { env, extraPathDirs } = params; const platform = params.platform ?? process.platform; - const sharedEnv = resolveSharedServiceEnvironmentFields(env, platform, extraPathDirs, params.execPath); + const sharedEnv = resolveSharedServiceEnvironmentFields( + env, + platform, + extraPathDirs, + params.execPath, + ); const gatewayToken = env.OPENCLAW_GATEWAY_TOKEN?.trim() || env.CLAWDBOT_GATEWAY_TOKEN?.trim() || undefined; return {