Skip to content

fix(install): auto-write NODE_EXTRA_CA_CERTS to .env on nvm Node.js installs (Linux)#51146

Merged
steipete merged 2 commits intoopenclaw:mainfrom
GodsBoy:fix/nvm-node-extra-ca-certs-v2
Mar 21, 2026
Merged

fix(install): auto-write NODE_EXTRA_CA_CERTS to .env on nvm Node.js installs (Linux)#51146
steipete merged 2 commits intoopenclaw:mainfrom
GodsBoy:fix/nvm-node-extra-ca-certs-v2

Conversation

@GodsBoy
Copy link
Copy Markdown
Contributor

@GodsBoy GodsBoy commented Mar 20, 2026

Summary

  • Detect nvm-installed Node.js on Linux (via NVM_DIR env var or process.execPath containing /.nvm/)
  • Auto-set NODE_EXTRA_CA_CERTS to the system CA bundle in the systemd service environment when nvm is detected on Linux (same pattern as the existing macOS default)
  • Probe multiple distro-specific CA bundle paths (/etc/ssl/certs/ca-certificates.crt for Debian/Ubuntu/Alpine, /etc/pki/tls/certs/ca-bundle.crt for RHEL/Fedora/CentOS, /etc/ssl/ca-bundle.pem for openSUSE) — returns undefined when none exists, so NODE_EXTRA_CA_CERTS is never set to a missing path
  • Write NODE_EXTRA_CA_CERTS to ~/.openclaw/.env during openclaw gateway install so manual gateway run also picks up the system CA bundle via dotenv
  • Backfill .env on the already-installed path so upgrading users get the entry without --force
  • Injectable platform/execPath params throughout for testability
  • Add documentation for nvm/VPS users with the manual workaround

Fixes #49088

Test plan

  • isNvmNode() unit tests: NVM_DIR env detection, execPath detection, negative cases
  • resolveLinuxSystemCaBundle() probes multiple distro paths, returns undefined when none exists
  • Service env tests: NODE_EXTRA_CA_CERTS defaults on Linux with/without nvm, user override respected
  • execPath injectable in both buildServiceEnvironment and buildNodeServiceEnvironment
  • All 65 service-env tests pass
  • TypeScript typecheck passes (0 errors)
  • Lint/format passes

@openclaw-barnacle openclaw-barnacle bot added docs Improvements or additions to documentation channel: msteams Channel integration: msteams gateway Gateway runtime cli CLI command changes size: M labels Mar 20, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 20, 2026

Greptile Summary

This PR fixes TLS failures for nvm-installed Node.js on Linux by auto-detecting nvm (via NVM_DIR or process.execPath) and writing NODE_EXTRA_CA_CERTS to the system CA bundle path in both the systemd service environment and ~/.openclaw/.env. It also backfills .env on the already-installed path (so upgrading users get the fix without --force), documents the change, and cleans up pre-existing type errors in the msteams extension tests.

  • The core detection and CA-bundle probe logic in service-env.ts is well-designed: injectable, tested, and safe (never writes a path that doesn't exist on disk).
  • The .env guard existing.includes("NODE_EXTRA_CA_CERTS") will silently skip writing the active entry if a commented-out line is already present — a regex anchored to uncommented lines would be more robust.
  • ensureNvmCaCertsInDotEnv in install.ts hard-codes process.platform and process.execPath, making it untestable without monkey-patching and inconsistent with the injectable execPath pattern used everywhere else in this PR.

Confidence Score: 4/5

  • Safe to merge; the two style-level issues are minor and do not affect correctness in the common case.
  • Logic is sound and well-tested. The .includes guard edge-case and the process.platform/process.execPath coupling are polish issues rather than runtime bugs. All existing tests pass and the new tests provide meaningful coverage.
  • src/cli/daemon-cli/install.ts — the two comments above apply here; no other files require special attention.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/cli/daemon-cli/install.ts
Line: 161

Comment:
**`includes` check can match commented-out entries**

`existing.includes("NODE_EXTRA_CA_CERTS")` will match a commented-out line such as `# NODE_EXTRA_CA_CERTS=/old/path`, silently preventing the active entry from ever being written. Consider anchoring to only uncommented occurrences:

```suggestion
    if (/^NODE_EXTRA_CA_CERTS=/m.test(existing)) {
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/cli/daemon-cli/install.ts
Line: 146-148

Comment:
**Hardcoded `process.platform` / `process.execPath` breaks testability**

`ensureNvmCaCertsInDotEnv` reads `process.platform` and `process.execPath` directly, while both `buildServiceEnvironment` and `buildNodeServiceEnvironment` accept `execPath` as an injectable parameter for exactly this reason. The function is not covered by unit tests because of this, unlike every other piece of nvm-detection logic in this PR.

Consider threading `platform` and `execPath` through `params` (or a separate argument) so the function can be tested the same way the service-env builders are:

```typescript
function ensureNvmCaCertsInDotEnv(params: {
  env: Record<string, string | undefined>;
  json: boolean;
  warnings: string[];
  platform?: NodeJS.Platform;   // defaults to process.platform
  execPath?: string;            // defaults to process.execPath
}): void {
  const platform = params.platform ?? process.platform;
  const execPath = params.execPath ?? process.execPath;
  if (platform !== "linux" || !isNvmNode(params.env, execPath)) {
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "fix(install): inject..."

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")) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 includes check can match commented-out entries

existing.includes("NODE_EXTRA_CA_CERTS") will match a commented-out line such as # NODE_EXTRA_CA_CERTS=/old/path, silently preventing the active entry from ever being written. Consider anchoring to only uncommented occurrences:

Suggested change
if (existing.includes("NODE_EXTRA_CA_CERTS")) {
if (/^NODE_EXTRA_CA_CERTS=/m.test(existing)) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cli/daemon-cli/install.ts
Line: 161

Comment:
**`includes` check can match commented-out entries**

`existing.includes("NODE_EXTRA_CA_CERTS")` will match a commented-out line such as `# NODE_EXTRA_CA_CERTS=/old/path`, silently preventing the active entry from ever being written. Consider anchoring to only uncommented occurrences:

```suggestion
    if (/^NODE_EXTRA_CA_CERTS=/m.test(existing)) {
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +146 to +148
if (process.platform !== "linux" || !isNvmNode(params.env, process.execPath)) {
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hardcoded process.platform / process.execPath breaks testability

ensureNvmCaCertsInDotEnv reads process.platform and process.execPath directly, while both buildServiceEnvironment and buildNodeServiceEnvironment accept execPath as an injectable parameter for exactly this reason. The function is not covered by unit tests because of this, unlike every other piece of nvm-detection logic in this PR.

Consider threading platform and execPath through params (or a separate argument) so the function can be tested the same way the service-env builders are:

function ensureNvmCaCertsInDotEnv(params: {
  env: Record<string, string | undefined>;
  json: boolean;
  warnings: string[];
  platform?: NodeJS.Platform;   // defaults to process.platform
  execPath?: string;            // defaults to process.execPath
}): void {
  const platform = params.platform ?? process.platform;
  const execPath = params.execPath ?? process.execPath;
  if (platform !== "linux" || !isNvmNode(params.env, execPath)) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cli/daemon-cli/install.ts
Line: 146-148

Comment:
**Hardcoded `process.platform` / `process.execPath` breaks testability**

`ensureNvmCaCertsInDotEnv` reads `process.platform` and `process.execPath` directly, while both `buildServiceEnvironment` and `buildNodeServiceEnvironment` accept `execPath` as an injectable parameter for exactly this reason. The function is not covered by unit tests because of this, unlike every other piece of nvm-detection logic in this PR.

Consider threading `platform` and `execPath` through `params` (or a separate argument) so the function can be tested the same way the service-env builders are:

```typescript
function ensureNvmCaCertsInDotEnv(params: {
  env: Record<string, string | undefined>;
  json: boolean;
  warnings: string[];
  platform?: NodeJS.Platform;   // defaults to process.platform
  execPath?: string;            // defaults to process.execPath
}): void {
  const platform = params.platform ?? process.platform;
  const execPath = params.execPath ?? process.execPath;
  if (platform !== "linux" || !isNvmNode(params.env, execPath)) {
```

How can I resolve this? If you propose a fix, please make it concise.

@steipete
Copy link
Copy Markdown
Contributor

Maintainer triage note: duplicate sweep did not find another PR for this exact issue beyond the earlier closed #49091.

Treating #51146 as the active successor to #49091 for #49088. Related issues like #26252, #22856, #48117, and #49556 are adjacent but not duplicates.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1e7ae8247

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if (!caBundle) {
return;
}
const stateDir = resolveGatewayStateDir(params.env);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Write NODE_EXTRA_CA_CERTS to the active dotenv state dir

ensureNvmCaCertsInDotEnv writes to resolveGatewayStateDir, but CLI dotenv loading uses resolveStateDir (loadCliDotEnv) to pick the active state directory, including legacy dirs (~/.clawdbot/~/.moldbot) or CLAWDBOT_STATE_DIR. For users still on a legacy state dir, this new write goes to ~/.openclaw/.env while openclaw gateway run reads a different .env, so the intended TLS fix is silently not applied on manual runs.

Useful? React with 👍 / 👎.

@GodsBoy GodsBoy force-pushed the fix/nvm-node-extra-ca-certs-v2 branch from d1e7ae8 to 7345a70 Compare March 20, 2026 17:02
@openclaw-barnacle openclaw-barnacle bot removed the channel: msteams Channel integration: msteams label Mar 20, 2026
@GodsBoy
Copy link
Copy Markdown
Contributor Author

GodsBoy commented Mar 20, 2026

Addressed both review comments in 7345a70:

  1. includes → regex anchor: existing.includes("NODE_EXTRA_CA_CERTS") replaced with /^NODE_EXTRA_CA_CERTS=/m.test(existing) so commented-out lines don't prevent writing the active entry.

  2. Injectable platform/execPath: ensureNvmCaCertsInDotEnv now accepts optional platform and execPath params (defaulting to process.platform/process.execPath), consistent with the service-env builders.

Re: Codex P1 (state dir mismatch) — ensureNvmCaCertsInDotEnv intentionally writes to resolveGatewayStateDir because the .env is consumed by gateway run, which uses the same resolver. The CLI's loadCliDotEnv path via resolveStateDir is a separate concern (CLI-level env, not gateway-level). Legacy CLAWDBOT_STATE_DIR users would need the manual workaround documented in the environment docs.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb3b871e44

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if (!caBundle) {
return;
}
const stateDir = resolveGatewayStateDir(params.env);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Write NODE_EXTRA_CA_CERTS to the CLI-active dotenv path

ensureNvmCaCertsInDotEnv writes into resolveGatewayStateDir, but CLI startup still loads .env from resolveStateDir (src/cli/dotenv.ts), which can resolve to a different directory (legacy state dirs and env-only profile setups). That means the new NODE_EXTRA_CA_CERTS entry can be written to a file that openclaw gateway run never reads, so the TLS fix is silently skipped for affected installs. Fresh evidence: src/cli/run-main.ts calls loadCliDotEnv directly, and resolveStateDir does not use OPENCLAW_PROFILE unless OPENCLAW_STATE_DIR is explicitly set.

Useful? React with 👍 / 👎.

@steipete steipete force-pushed the fix/nvm-node-extra-ca-certs-v2 branch from fb3b871 to ce7ba11 Compare March 21, 2026 17:48
@steipete steipete requested a review from a team as a code owner March 21, 2026 17:48
@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: L and removed size: M labels Mar 21, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ce7ba11157

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +227 to +230
const expectedNodeExtraCaCerts = resolveAutoNodeExtraCaCerts({
env: params.env,
execPath: process.execPath,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Detect stale nvm services from the installed command

When openclaw gateway install is run on an already-installed service, this refresh check computes the expected CA bundle from the current CLI process (process.execPath and params.env) instead of the executable that the service is actually configured to run. That misses the main upgrade case this patch is trying to fix: an older gateway service still points at ~/.nvm/.../node, but the user now invokes openclaw gateway install from a system Node or Bun shell. In that scenario resolveAutoNodeExtraCaCerts(...) returns undefined, the reinstall is skipped, and the existing nvm-backed daemon keeps failing TLS. readCommand() already returns programArguments, so the check should key off currentCommand.programArguments[0] rather than the installer's runtime.

Useful? React with 👍 / 👎.

@steipete steipete force-pushed the fix/nvm-node-extra-ca-certs-v2 branch from ce7ba11 to 2e6cf19 Compare March 21, 2026 17:56
@openclaw-barnacle openclaw-barnacle bot removed the agents Agent runtime and tooling label Mar 21, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2e6cf19c0f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (!expectedNodeExtraCaCerts) {
return false;
}
return currentNodeExtraCaCerts !== expectedNodeExtraCaCerts;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve custom NODE_EXTRA_CA_CERTS on installed services

If an existing gateway service intentionally sets NODE_EXTRA_CA_CERTS to a non-default bundle (for example a corporate CA file), this check still treats the service as stale and forces a reinstall because it only accepts the auto-detected system path. In that scenario a plain openclaw gateway install stops being a no-op: buildGatewayInstallPlan rebuilds the service environment from the current shell/config, so the service-local CA bundle is dropped and TLS can break after the refresh.

Useful? React with 👍 / 👎.

Comment on lines +353 to +356
: resolveAutoNodeExtraCaCerts({
env,
platform,
execPath,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass the chosen runtime path into Linux CA auto-detection

This new Linux branch only works when callers supply execPath; otherwise resolveAutoNodeExtraCaCerts falls back to the installer's process.execPath. buildGatewayInstallPlan and buildNodeInstallPlan still call build*ServiceEnvironment without that argument, so any flow that targets an nvm-managed Node via nodePath while the installer itself runs under Bun or a system Node will generate a service whose programArguments[0] points at ~/.nvm/.../node but whose environment omits NODE_EXTRA_CA_CERTS, leaving the TLS failure unchanged.

Useful? React with 👍 / 👎.

@steipete steipete merged commit ebfc5f8 into openclaw:main Mar 21, 2026
64 of 66 checks passed
@steipete
Copy link
Copy Markdown
Contributor

Landed via temp rebase onto main.

  • Gate: pnpm test -- src/bootstrap/node-extra-ca-certs.test.ts src/entry.respawn.test.ts src/daemon/service-env.test.ts src/cli/daemon-cli/install.test.ts; local pnpm build hit an unrelated Baileys unresolved-import on latest main; local full OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test exposed unrelated pre-existing failures outside this patch; PR CI passed the substantive build/test/install lanes, and the only red lane was a formatter false positive on src/cli/daemon-cli/install.ts and src/cli/daemon-cli/install.test.ts that I could not reproduce locally or on the synthetic merge ref.
  • Source head: 2e6cf19
  • Merge commit: ebfc5f8

Thanks @GodsBoy!

mrosmarin added a commit to mrosmarin/openclaw that referenced this pull request Mar 21, 2026
* main: (516 commits)
  fix: use content hash for memory flush dedup instead of compactionCount (openclaw#30115) (openclaw#34222)
  fix(tts): add matrix to VOICE_BUBBLE_CHANNELS (openclaw#37080)
  feat(memory): pluggable system prompt section for memory plugins (openclaw#40126)
  fix: detect nvm services from installed command (openclaw#51146)
  fix: handle Linux nvm CA env before startup (openclaw#51146) (thanks @GodsBoy)
  refactor: route Telegram runtime through plugin sdk (openclaw#51772)
  refactor: route iMessage runtime through plugin sdk (openclaw#51770)
  refactor: route Slack runtime through plugin sdk (openclaw#51766)
  refactor(doctor): extract provider and shared config helpers (openclaw#51753)
  Fix Discord `/codex_resume` picker expiration (openclaw#51260)
  fix(ci): remove duplicate embedding default export
  fix(ci): restore embedding defaults and plugin boundaries
  fix: compaction safeguard summary budget (openclaw#27727)
  web UI: fix context notice using accumulated inputTokens instead of prompt snapshot (openclaw#51721)
  fix(status): skip cold-start status probes
  refactor(doctor): extract telegram provider warnings (openclaw#51704)
  fix(telegram): default fresh setups to mention-gated groups
  docs(changelog): note telegram doctor first-run guidance
  fix(doctor): add telegram first-run guidance
  fix(doctor): suppress telegram fresh-install group warning
  ...
JohnJAS pushed a commit to JohnJAS/openclaw that referenced this pull request Mar 22, 2026
JohnJAS pushed a commit to JohnJAS/openclaw that referenced this pull request Mar 22, 2026
pholpaphankorn pushed a commit to pholpaphankorn/openclaw that referenced this pull request Mar 22, 2026
pholpaphankorn pushed a commit to pholpaphankorn/openclaw that referenced this pull request Mar 22, 2026
MaheshBhushan pushed a commit to MaheshBhushan/openclaw that referenced this pull request Mar 22, 2026
MaheshBhushan pushed a commit to MaheshBhushan/openclaw that referenced this pull request Mar 22, 2026
frankekn pushed a commit to artwalker/openclaw that referenced this pull request Mar 23, 2026
frankekn pushed a commit to artwalker/openclaw that referenced this pull request Mar 23, 2026
furaul pushed a commit to furaul/openclaw that referenced this pull request Mar 24, 2026
furaul pushed a commit to furaul/openclaw that referenced this pull request Mar 24, 2026
dustin-olenslager pushed a commit to dustin-olenslager/ironclaw-supreme that referenced this pull request Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes docs Improvements or additions to documentation gateway Gateway runtime size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] web_fetch fails silently on Linux/VPS when Node.js installed via nvm (TLS cert bundle missing ISRG/DigiCert roots)

2 participants