[BUG] Desktop Code tab: NODE_USE_SYSTEM_CA=1 in sandboxed Bash makes every Node TLS init print "failed to copy trust settings" errors; settings env can't override it

Status Open
Reported on v2.1.293
Maintainer reply None cached
Activity 0 comments · opened Oct 8, 2026

Environment

  • Claude desktop app 2.26454.2, Code tab, local session; bundled Claude Code 2.1.293 (CLAUDE_CODE_ENTRYPOINT=claude-desktop)
  • macOS 26.2 (25C56), arm64
  • Bash tool sandbox enabled (Seatbelt); sandbox.enableWeakerNetworkIsolation not set
  • zsh 5.9, Node v26.8.1 (nvm), pnpm 11.28.2
  • NODE_USE_SYSTEM_CA, NODE_EXTRA_CA_CERTS and NODE_OPTIONS are not set in my shell profiles, nvm.sh, launchctl getenv or LaunchAgents

What happens
Every Bash command in a Code-tab session, including subagents' commands, inherits NODE_USE_SYSTEM_CA=1. With that set, Node reads the macOS keychain whenever it builds its default TLS context. Inside the sandbox it cannot read the certificates' trust settings, so it writes one line to stderr per failed lookup:

ERROR: failed to copy trust settings of system certificate-25291

-25291 is the OSStatus errSecNotAvailable, which Node's fprintf appends with no separator (src/crypto/crypto_context.cc, IsCertificateTrustedForPolicy). Node checks the User and Admin trust domains for every certificate in the keychain search list, so the count is 2 × the number of certificates. Here that is 58 lines (29 certificates: 26 in the login keychain, 3 in System.keychain).

The variable gives nothing in return inside the sandbox: because every lookup fails, Node trusts zero system certificates (tls.getCACertificates('system').length is 0 inside the sandbox and 8 outside).

Any Node tool that sets up TLS adds these lines. pnpm 11 does so at startup, so every pnpm --version, pnpm --filter <pkg> typecheck, pnpm test, etc. adds 58 lines to the agent's context. With many pnpm calls across subagents, this reaches thousands of lines per session. (node -e 1 and npm --version print nothing, since they don't set up TLS.)

Where the variable comes from

  • The bundled CLI process (~/Library/Application Support/Claude/claude-code/2.1.293/.../claude) is started with NODE_USE_SYSTEM_CA=1 (ps eww on the Bash shell's parent). The desktop app's main process does not have it.
  • In Claude.app/Contents/Resources/app.asar (2.26454.2, minified), the Code-session env is built as:

``js
sessionEnv:{...l,DISABLE_MICROCOMPACT:"1",CLAUDE_CODE_TERMINAL_MCP_TOOLS:…,NODE_USE_SYSTEM_CA:"1"}
`
As far as I can tell,
l is the env merged from process.env, the Local environment editor's variables and scoped variables. The literal comes last, so it overrides any value set there. (A separate CA helper in the bundle also returns NODE_USE_SYSTEM_CA:"1" when no CA bundle is primed, otherwise NODE_EXTRA_CA_CERTS`.)

  • The standalone CLI (Homebrew cask 2.1.285) does not set the variable: in both CLI binaries the string only appears in Bun's own TLS code. So this seems specific to sessions the desktop app starts.

Repro (Code-tab local session, sandbox on, no workaround)

echo "[$NODE_USE_SYSTEM_CA]"
# [1]
node -e "require('node:https').get('https://example.invalid').on('error',()=>{})" 2>&1 | grep -c 'failed to copy trust settings'
# 58
NODE_USE_SYSTEM_CA=0 node -e "require('node:https').get('https://example.invalid').on('error',()=>{})" 2>&1 | grep -c 'failed to copy trust settings'
# 0

The blocked service, from a plain terminal outside Claude Code:

printf '(version 1)\n(allow default)\n(deny mach-lookup (global-name "com.apple.trustd.agent"))\n' > /tmp/deny-trustd-agent.sb
NODE_USE_SYSTEM_CA=1 sandbox-exec -f /tmp/deny-trustd-agent.sb node -e "require('node:https').get('https://example.invalid').on('error',()=>{})" 2>&1 | grep -c 'failed to copy trust settings'
# 58 (0 without the deny rule)

Denying only com.apple.trustd.agent reproduces the same 58 lines and the empty system store. The profile that 2.1.293 generates allows that service only when sandbox.enableWeakerNetworkIsolation is true.

What I tried

  • "env": { "NODE_USE_SYSTEM_CA": "0" } in ~/.claude/settings.json, then fully quitting and reopening the app: no effect. This matches the settings reference: when the desktop app starts the session, its launch environment wins over a settings env value for the same variable.
  • The Local environment editor from the desktop docs (environment dropdown → Local → gear icon): I could not find it in my build. Going by the code above, its value would be overridden anyway.
  • CLAUDE_CODE_CERT_STORE does not help: it only selects the CA sources Claude Code itself trusts, and the CLI never sets or clears NODE_USE_SYSTEM_CA for subprocesses.

Workaround (Bash tool only; main agent and subagents both drop to 0 lines in new sessions)

"hooks": {
  "SessionStart": [
    {
      "hooks": [
        {
          "type": "command",
          "command": "[ -n \"$CLAUDE_ENV_FILE\" ] && echo 'export NODE_USE_SYSTEM_CA=0' >> \"$CLAUDE_ENV_FILE\"; exit 0"
        }
      ]
    }
  ]
}

Going by the sandbox-exec result, sandbox.enableWeakerNetworkIsolation: true should also stop the errors. I have not tried it in a live session, and the docs say it reduces security.

Expected
Any one of these would fix it:

  • Don't pass NODE_USE_SYSTEM_CA=1 to sandboxed Bash commands. Inside the sandbox it cannot work (0 system certificates) and only adds noise.
  • Let a user setting (settings env or the Local environment editor) override it. Today the literal is applied last.
  • If Node tools should trust OS-installed CAs inside the sandbox, allow com.apple.trustd.agent for that purpose without tying it to enableWeakerNetworkIsolation.

Related

  • #34876 (closed as not planned) and #82793 (open): the macOS sandbox denies the Mach lookup that Go needs for TLS verification, and #82793 shows the service is com.apple.trustd.agent. The sandbox-exec test above shows this Node error comes from the same denial.
  • pmark/arcadia#890: another project reports the same failed to copy trust settings of system certificate-25291 lines (~16 per call) on every mise exec -- node ... call inside the Claude Code sandbox. That report does not identify a root cause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

View original on GitHub ↗