[BUG] NODE_OPTIONS=--use-system-ca replaces instead of augments the bundled CA store — confirmed regression #72066 was auto-closed as not_planned

Status Fixed / completed
Reported on v2.1.252
Maintainer reply None cached
Activity 0 comments · opened Sep 2, 2026 · closed Oct 15, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

NODE_OPTIONS=--use-system-ca makes Claude Code discard its bundled public root certificates
and trust only the OS certificate store. On a managed macOS machine the OS store as read
here contains just the admin/user-added organization certificates and no public roots, so every
endpoint anchored on a public root fails TLS verification and Claude Code cannot start a
conversation.

Claude Code's own debug log shows the whole thing in two lines. Without the flag:

CA certs: stores=bundled,system, extraCertsPath=undefined
CA certs: Loaded 121 bundled root certificates
CA certs: Loaded 9 system CA certificates

With NODE_OPTIONS=--use-system-ca:

CA certs: stores=system, extraCertsPath=undefined
CA certs: Loaded 9 system CA certificates

121 bundled roots are dropped, and the 9 that remain are organization certificates that
cannot anchor any public chain.

This is the regression already confirmed and reproduced by a maintainer in #72066, which the
lifecycle sweep closed as not_planned about nine hours before that confirmation was posted.
The issue was never reopened, no other open issue tracks it, and the behaviour is unchanged in
2.1.252.

I am filing a new issue only because #72066 is closed and its closing comment instructs
reporters to do so. Please reopen or relink #72066 rather than re-triaging this from scratch
— the diagnosis there is already correct. Timeline:

| When (UTC) | Event |
|---|---|
| 2026-06-28 | Reported |
| 2026-08-01 | Labeled stale by the sweep |
| 2026-08-16 10:10 | Auto-closed as not_planned (14 days after the stale label) |
| 2026-08-16 18:58 | Maintainer confirms and reproduces on 2.1.233, dates the regression to ~2.1.70, states "We'll fix Claude Code to treat the flag additively, matching Node.", and adds the regression and reproduced labels — but does not reopen |
| 2026-08-16 18:59 | Sweep removes the stale label |
| 2026-09-01 | Still reproduces on 2.1.252 |

So the labels read bug / reproduced / regression / has repro while the state is
closed / not_planned, which keeps it out of is:open searches.

Root cause, unchanged in 2.1.252. As the maintainer explained in #72066, and as the debug log
above shows, when NODE_OPTIONS contains --use-system-ca the resolved store list becomes
system instead of the default bundled,system. The OS store replaces the bundled roots
instead of being added on top of them. NODE_OPTIONS=--use-openssl-ca produces the same
stores=system result in the debug log.

This is not documented behaviour. On the page linked from #72066
(https://code.claude.com/docs/en/network-config#ca-certificate-store) the strings
use-system-ca, use-openssl-ca and NODE_OPTIONS do not appear at all — zero occurrences
each. What the page does say is the opposite: *"By default, Claude Code trusts both its
bundled Mozilla CA certificates and your operating system's certificate store"*, and
"CLAUDE_CODE_CERT_STORE … The default is bundled,system." So an environment variable that
silently narrows that to one source is undocumented, and cannot be read as intended policy.

I want to address one likely response up front: #72066 notes that *"you shouldn't need
--use-system-ca for Claude Code at all"*, which is true — Claude Code has read the OS store by
default since 2.1.101. But nobody sets this variable for Claude Code. It is set for npm /
pnpm, and NODE_OPTIONS is process-global, so Claude Code picks it up from the same shell.
"You don't need it" therefore doesn't help: the user cannot scope it away without knowing that
Claude Code reads NODE_OPTIONS at all, which is not documented anywhere. That is what makes
this a bug rather than a configuration mistake.

Why it is easy to hit. NODE_OPTIONS=--use-system-ca is a reasonable thing to have
exported — it is a documented way to make npm / pnpm trust an internally-issued
certificate. In my case the line had been in ~/.zshrc for three days for exactly that reason,
from unrelated work. The user never asks Claude Code for anything: it opts itself into another
runtime's environment variable and then interprets it more aggressively than that runtime does.

What Should Happen?

--use-system-ca should be additive, matching Node.js: the OS certificate store on top of
the bundled roots, not instead of them. The resolved store should stay bundled,system — i.e.
identical to the default — so the flag becomes a no-op for Claude Code rather than a
connectivity failure.

For reference, this is what Node.js v24.15.0 actually does on the same machine.
default is the set actually used for verification:

| Invocation | certs used | bundled certs missing from the used set | Semantics |
|---|---|---|---|
| node (no flag) | 145 | 0 | — |
| node --use-system-ca | 156 | 0 | additive |
| node --use-openssl-ca | 0 | 145 | replacement |
| claude with NODE_OPTIONS=--use-system-ca | 9 | all 121 | replacement |

Measured with:

$ node --use-system-ca -e 'const t=require("tls");
  const d=new Set(t.getCACertificates("default")), b=new Set(t.getCACertificates("bundled"));
  let m=0; for (const c of b) if (!d.has(c)) m++;
  console.log(`default=${d.size} bundled=${b.size} bundled_missing=${m}`)'
default=156 bundled=145 bundled_missing=0

Node treats the two flags differently. Claude Code collapses them into one branch and makes
both replacing.

Node docs for the flag: https://nodejs.org/api/cli.html#--use-system-ca
Claude Code docs for the store: https://code.claude.com/docs/en/network-config#ca-certificate-store

Error Messages/Logs

$ NODE_OPTIONS=--use-system-ca claude -p 'Reply with just OK'
API Error: Unable to connect to API: SSL certificate verification failed.
Check your proxy or corporate SSL certificates

# --debug-file, with the flag set
[DEBUG] CA certs: stores=system, extraCertsPath=undefined
[DEBUG] CA certs: Loaded 9 system CA certificates
[ERROR] Connection error details: code=UNABLE_TO_GET_ISSUER_CERT_LOCALLY (SSL error), message=unable to get local issuer certificate

# --debug-file, same machine, flag unset
[DEBUG] CA certs: stores=bundled,system, extraCertsPath=undefined
[DEBUG] CA certs: Loaded 121 bundled root certificates
[DEBUG] CA certs: Loaded 9 system CA certificates

Steps to Reproduce

Requires a macOS machine whose OS certificate store contains only admin/user-added
certificates and no public roots — i.e. any MDM-managed corporate Mac. No TLS-inspecting proxy
is needed; I verified with openssl s_client and curl that the failing path presents the
genuine upstream certificate with Verify return code: 0 (ok).

Note: --debug -p does not emit the cert-store lines. Use --debug-file to capture them.

  1. Confirm Claude Code works normally:

``console
$ claude -p 'Reply with just OK'
OK
``

  1. Set the Node flag exactly as one would for npm / pnpm, and re-run:

``console
$ NODE_OPTIONS=--use-system-ca claude -p 'Reply with just OK'
API Error: Unable to connect to API: SSL certificate verification failed.
Check your proxy or corporate SSL certificates
``

  1. Capture the resolved store both ways and compare:

``console
$ NODE_OPTIONS=--use-system-ca claude --debug-file /tmp/with.log -p 'hi' >/dev/null 2>&1
$ claude --debug-file /tmp/without.log -p 'hi' >/dev/null 2>&1
$ grep 'CA certs: \(stores\|Loaded\)' /tmp/with.log /tmp/without.log
/tmp/with.log: CA certs: stores=system, extraCertsPath=undefined
/tmp/with.log: CA certs: Loaded 9 system CA certificates
/tmp/without.log: CA certs: stores=bundled,system, extraCertsPath=undefined
/tmp/without.log: CA certs: Loaded 121 bundled root certificates
/tmp/without.log: CA certs: Loaded 9 system CA certificates
``

The 121 bundled roots are gone in the first case.

  1. Confirm the bundled store alone works, i.e. the machine and network are fine — only the

store selection is wrong:

``console
$ NODE_OPTIONS=--use-system-ca CLAUDE_CODE_CERT_STORE=bundled claude -p 'Reply with just OK'
OK
``

Note for anyone arriving from a search engine: removing the line from ~/.zshrc does not
fix an already-running shell — the variable is still exported in that process. Open a new
terminal or unset NODE_OPTIONS.

Claude Model

Not sure / Multiple models

Is this a regression?

Yes, this worked in a previous version

Last Working Version

Unknown — the maintainer dates the regression to roughly 2.1.70 in #72066.

Claude Code Version

2.1.252 (Claude Code)

Platform

AWS Bedrock

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

Environment

  • macOS 26.4.1 (build 25E253), arm64
  • Node.js v24.15.0 — used only as the reference implementation in the table above. Claude Code

itself is a compiled binary and does not run on Node, which is part of what makes this
surprising: NODE_OPTIONS has no legitimate effect on this process.

  • MDM-managed Mac. The system keychain holds organization certificates only.
  • No TLS-inspecting proxy on the failing path.

The failure is not specific to a model provider. Anthropic-owned endpoints fail in the same run:

Failed to fetch version from https://downloads.claude.ai/... : unable to get local issuer certificate

Two adjacent defects in the same line

Both concern the same store-selection step, so I have kept them here rather than opening more
issues. Happy to split either into
its own report if you prefer.

(a) --use-openssl-ca is aliased to the wrong store. In Node, --use-openssl-ca selects
OpenSSL's default certificate store (SSL_CERT_FILE / SSL_CERT_DIR) — a different
location from the macOS keychains. On this machine it resolves to an empty set (0 certificates,
see the table above). Mapping it to "system" silently redirects the user to macOS keychains
they did not ask for. Even after --use-system-ca is made additive, this would remain
incorrect. Suggest honouring SSL_CERT_FILE / SSL_CERT_DIR, or ignoring the flag with a
warning, rather than aliasing it.

(b) The degradation is silent, and the error message misdirects. Claude Code already warns on
bad explicit configuration — e.g. CLAUDE_CODE_CERT_STORE=foo logs:

[WARN] CA certs: unrecognized CLAUDE_CODE_CERT_STORE source 'foo', ignoring

But dropping 121 bundled roots because of a NODE_OPTIONS token produces no warning at all.
The only user-visible output is:

API Error: Unable to connect to API: SSL certificate verification failed.
Check your proxy or corporate SSL certificates

That points the user at their proxy and their organization's certificates — i.e. at their IT
department — when the actual cause is that Claude Code discarded its own public root bundle. On
a managed corporate Mac this is an expensive false lead: it cost me most of a day auditing
MDM-deployed certificates, TLS-inspection settings, MDM policy-change timestamps, and older
Claude Code versions before I found the one line in ~/.zshrc.

Two low-cost mitigations, independent of the additive fix:

  1. Warn whenever NODE_OPTIONS causes the resolved cert store to differ from the default,

naming the flag and the resulting store list.

  1. Include the resolved store list in TLS verification errors, e.g.

... (CA stores: system; 9 certificates loaded, 121 bundled roots not used). The debug log
already has this; surfacing it in the error itself would have ended this investigation
immediately.

Workarounds (confirmed on 2.1.252)

CLAUDE_CODE_CERT_STORE=bundled,system claude   # keeps organization certs working too
CLAUDE_CODE_CERT_STORE=bundled claude

Or scope the Node flag so it never reaches Claude Code, per the workaround in #72066:

alias node='NODE_OPTIONS=--use-system-ca node'

For the original npm / pnpm need, NODE_EXTRA_CA_CERTS=/path/to/internal-ca.pem is a better
fit than NODE_OPTIONS=--use-system-ca: it adds the one certificate that is actually required
and has no effect on which store Claude Code selects.

View original on GitHub ↗