fix(install): auto-write NODE_EXTRA_CA_CERTS to .env on nvm Node.js installs (Linux)#51146
Conversation
Greptile SummaryThis PR fixes TLS failures for nvm-installed Node.js on Linux by auto-detecting nvm (via
Confidence Score: 4/5
Prompt To Fix All With AIThis 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..." |
src/cli/daemon-cli/install.ts
Outdated
| 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")) { |
There was a problem hiding this 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:
| 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.
src/cli/daemon-cli/install.ts
Outdated
| if (process.platform !== "linux" || !isNvmNode(params.env, process.execPath)) { | ||
| return; | ||
| } |
There was a problem hiding this 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:
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.There was a problem hiding this comment.
💡 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".
src/cli/daemon-cli/install.ts
Outdated
| if (!caBundle) { | ||
| return; | ||
| } | ||
| const stateDir = resolveGatewayStateDir(params.env); |
There was a problem hiding this comment.
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 👍 / 👎.
d1e7ae8 to
7345a70
Compare
|
Addressed both review comments in 7345a70:
Re: Codex P1 (state dir mismatch) — |
There was a problem hiding this comment.
💡 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".
src/cli/daemon-cli/install.ts
Outdated
| if (!caBundle) { | ||
| return; | ||
| } | ||
| const stateDir = resolveGatewayStateDir(params.env); |
There was a problem hiding this comment.
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 👍 / 👎.
fb3b871 to
ce7ba11
Compare
There was a problem hiding this comment.
💡 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".
src/cli/daemon-cli/install.ts
Outdated
| const expectedNodeExtraCaCerts = resolveAutoNodeExtraCaCerts({ | ||
| env: params.env, | ||
| execPath: process.execPath, | ||
| }); |
There was a problem hiding this comment.
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 👍 / 👎.
ce7ba11 to
2e6cf19
Compare
There was a problem hiding this comment.
💡 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; |
There was a problem hiding this comment.
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 👍 / 👎.
| : resolveAutoNodeExtraCaCerts({ | ||
| env, | ||
| platform, | ||
| execPath, |
There was a problem hiding this comment.
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 👍 / 👎.
|
Landed via temp rebase onto
Thanks @GodsBoy! |
* 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 ...
Summary
NVM_DIRenv var orprocess.execPathcontaining/.nvm/)NODE_EXTRA_CA_CERTSto the system CA bundle in the systemd service environment when nvm is detected on Linux (same pattern as the existing macOS default)/etc/ssl/certs/ca-certificates.crtfor Debian/Ubuntu/Alpine,/etc/pki/tls/certs/ca-bundle.crtfor RHEL/Fedora/CentOS,/etc/ssl/ca-bundle.pemfor openSUSE) — returnsundefinedwhen none exists, soNODE_EXTRA_CA_CERTSis never set to a missing pathNODE_EXTRA_CA_CERTSto~/.openclaw/.envduringopenclaw gateway installso manualgateway runalso picks up the system CA bundle via dotenv.envon thealready-installedpath so upgrading users get the entry without--forceplatform/execPathparams throughout for testabilityFixes #49088
Test plan
isNvmNode()unit tests: NVM_DIR env detection, execPath detection, negative casesresolveLinuxSystemCaBundle()probes multiple distro paths, returns undefined when none existsexecPathinjectable in bothbuildServiceEnvironmentandbuildNodeServiceEnvironment