CERT_HAS_EXPIRED on Windows native install during OAuth login, no proxy/VPN/AV — curl succeeds on same host

Status Closed — not planned
Reported on v2.1.193
Maintainer reply None cached
Activity 14 comments · opened Jun 26, 2026 · closed Aug 23, 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?

claude /login fails with CERT_HAS_EXPIRED connecting to platform.claude.com during OAuth token exchange. Browser authorize step succeeds; CLI token exchange fails.

curl -vI https://platform.claude.com from same machine returns clean HTTP 200, valid cert. No proxy. VPN on/off tested, no change. No third-party AV (Defender only). System clock correct.

CLAUDE_CODE_CERT_STORE=system and BUN_USE_OPENSSL_CA=1 tried, no effect.

claude doctor shows "Search: OK (bundled)" succeeding but "Failed to fetch versions" failing in same run.

What Should Happen?

Login should complete, consistent with curl's successful TLS handshake to the same host.

Error Messages/Logs

Unable to connect to Anthropic services
Failed to connect to platform.claude.com: CERT_HAS_EXPIRED
SSL certificate error (CERT_HAS_EXPIRED). If you are behind a corporate proxy or TLS-intercepting firewall, set NODE_EXTRA_CA_CERTS to your CA bundle path, or ask IT to allowlist *.anthropic.com. Run /doctor for details.

Steps to Reproduce

  1. Install Claude Code on Windows via winget (Anthropic.ClaudeCode)
  2. Open PowerShell
  3. Run: claude auth /login
  4. Browser opens, click Authorize
  5. CLI fails to complete token exchange with: CERT_HAS_EXPIRED connecting to platform.claude.com
  6. Run: curl -vI https://platform.claude.com (in same shell) — returns clean HTTP 200, valid certificate

Claude Model

Not sure / Multiple models

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.193

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Windows Terminal

Additional Information

Auto-update attempted to 2.1.195 per claude doctor.

View original on GitHub ↗

13 Comments

github-actions[bot] · 2 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/71581
  2. https://github.com/anthropics/claude-code/issues/71554
  3. https://github.com/anthropics/claude-code/issues/71663

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

yurukusa · 2 months ago

The dup-bot is likely misfiling this — #71663 was a relative NODE_EXTRA_CA_CERTS path (a config error), and you've already ruled out the proxy / clock / AV causes that the canned error message assumes. The "curl succeeds, CLI fails with CERT_HAS_EXPIRED on the same host" pattern points at something more specific.

The native CLI doesn't share curl's TLS stack. The native build runs on Bun, which validates TLS against its own bundled CA/trust store (BoringSSL) — separate from the OS/system store that curl (and Defender) use. So a clean curl -vI to platform.claude.com tells you the system path is fine, but says nothing about the path Bun builds. That's why BUN_USE_OPENSSL_CA=1 and CLAUDE_CODE_CERT_STORE=system "having no effect" is itself a clue — the native build may not be honoring those, so it's still validating against the bundled store.

CERT_HAS_EXPIRED specifically (not UNABLE_TO_GET_ISSUER...) means a cert in the presented/built chain has a NotAfter in the past — and the classic "curl is fine but the stricter stack isn't" cause is an expired cross-signed root/intermediate that OpenSSL/curl routes around (it finds an alternate valid path) while the bundled store picks the expired link and stops. The fact that claude doctor shows Search: OK (bundled) but Failed to fetch versions failing in the same run fits: different hosts/endpoints, different chains, same runtime.

To pin it down:

# what chain is actually presented, and is anything already expired?
openssl s_client -connect platform.claude.com:443 -servername platform.claude.com -showcerts </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -subject -dates
# and run the CLI with debug to see the exact host/cert it rejects:
claude --debug /login 2>&1 | tail -60

Look for any notAfter= date in the past in the presented chain or the path your local store builds.

Workarounds to try, in order:

  1. Update the native build (claude install / latest) — a newer Bun ships a newer bundled CA store, which is the cleanest fix if the bundled store is what's stale.
  2. **Point at a fresh CA bundle via an absolute path. Since BUN_USE_OPENSSL_CA=1 didn't take, try giving it a current bundle explicitly: download Mozilla's cacert.pem (e.g. from the curl project) and set NODE_EXTRA_CA_CERTS=C:\full\path\to\cacert.pemabsolute path**, and export/set it in the shell before launching (settings.json env may apply too late for the first TLS handshake; #71663 was exactly a relative-path version of this).
  3. If openssl s_client shows an expired cross-signed root in the chain, distrusting/removing that specific expired root from the Windows store lets the validator fall through to the still-valid path.

If step 1 fixes it, the bundled CA store was stale; if step 2 fixes it, the native build isn't honoring the system store and needs an explicit bundle. Either way it's worth keeping this open separately from #71663 — the cause and fix are different.

---

For maintainers: the error text only suggests the corporate-proxy path, but this reporter has a clean curl + correct clock + no proxy, which the message doesn't cover. Surfacing which host and which cert (subject + notAfter) failed validation — and whether the native build actually honors CLAUDE_CODE_CERT_STORE=system / BUN_USE_OPENSSL_CA=1 — would separate "stale bundled store" from "expired cross-signed root in the chain" from "proxy MITM," which currently all collapse into one CERT_HAS_EXPIRED line.

LLamabobAL · 2 months ago

Confirming this on a second machine with a clean (non-corporate-network) repro, plus a few additional data points:
Environment: Windows 11, native installer (not winget), no proxy/VPN/AV (Defender only), system clock correct.
Symptom: Same exact error, but not limited to OAuth token exchange — happens on every connection attempt, including just running claude with no subcommand:
Unable to connect to Anthropic services
Failed to connect to platform.claude.com: CERT_HAS_EXPIRED
Additional env vars ruled out (on top of CLAUDE_CODE_CERT_STORE=system and BUN_USE_OPENSSL_CA=1 from the original report):

NODE_USE_SYSTEM_CA=1 — no effect
NODE_EXTRA_CA_CERTS pointed at the correct intermediate chain file — no effect

That's now 4 different env vars across two reports that don't fix it.
Cert chain validates cleanly via openssl, not just curl:
openssl s_client -connect platform.claude.com:443 -showcerts
...
Verify return code: 0 (ok)
Full chain: platform.claude.com → Let's Encrypt YE1 → ISRG Root YE → ISRG Root X2 → ISRG Root X1. Cert was issued 2026-06-26, well within validity. Note the chain uses the newer ISRG Root YE/YE1 intermediates rather than the older X1/R3 chain — my guess is the bundled CA store in the native binary doesn't yet recognize this newer chain and is misreporting an unrecognized-issuer failure as CERT_HAS_EXPIRED.
Workaround that fixed it for me: downgrading from the latest channel to stable:
claude update --channel stable
This dropped me from 2.1.195 → 2.1.181, and the connection succeeded immediately afterward with zero other changes. Might help narrow down which release introduced the regression — possibly tied to the Bun runtime bump mentioned in #71727.

bjones9942 · 2 months ago

With a winget install, uninstalling and then installing version 2.1.181 (winget install --id Anthropic.ClaudeCode --version 2.1.181) worked for me as well. Wiped my settings, but I was able to login and use the terminal/cli interface again.

Ishannaik · 2 months ago

Fix for Windows native install: CERT_HAS_EXPIRED during OAuth login

If npm install -g @anthropic-ai/claude-code@2.1.177 does not fix it and claude -v still shows 2.1.195, you have a native install shadowing the npm one.

Run where claude. If it lists C:\Users\<you>\.local\bin\claude.exe, that native binary is what actually runs, so npm downgrades never take effect.

The real fix is to downgrade the native install:

claude install 2.1.177

Then verify with claude -v (should read 2.1.177). OAuth login works again.

Root cause: versions ~2.1.181 through 2.1.195 bundle Bun 1.4.0, which has a TLS cert-verification regression that only breaks the OAuth login flow. 2.1.177 bundles Bun 1.3.14 and is unaffected. Auto-update can re-bump you, so disable it: set DISABLE_AUTOUPDATER=1 or autoUpdates: false in settings.json.

edev-us · 2 months ago

Confirming on the VS Code extension (anthropic.claude-code v2.1.195), not just the
native CLI — same CERT_HAS_EXPIRED on platform.claude.com during OAuth login. Windows,
no proxy/VPN/TLS-inspection, Defender only, clock correct. Reproduces by running the
extension's bundled binary directly (...\resources\native-binary\claude.exe), so it's
the shared Bun runtime, not the extension wrapper.

openssl s_client -showcerts confirms the served chain is fully valid (Gen Y, same as #71766):
CN=platform.claude.com notAfter Sep 24 2026
Let's Encrypt YE1 notAfter Sep 2 2028
ISRG Root YE notAfter Sep 2 2032
ISRG Root X2 notAfter Sep 2 2032

NODE_EXTRA_CA_CERTS with a roots-only Mozilla bundle does NOT resolve it; NODE_USE_SYSTEM_CA=1
is ignored. This is the Windows / CERT_HAS_EXPIRED variant of #71766.

jarnix · 2 months ago

It's happening to in 2.1.196

Basically can't use Claude.

bucheronbanlieusard · 2 months ago

set NODE_TLS_REJECT_UNAUTHORIZED=0 patched it for me

medalliaerlich · 2 months ago
set NODE_TLS_REJECT_UNAUTHORIZED=0 patched it for me

yes but that will be insecure

jarnix · 2 months ago

this fixed the issue:

claude install 2.1.177

spamstoper-star · 2 months ago

claude install 2.1.177

Thanks for this.

skohuspaycor · 2 months ago

Environment

  • OS: Windows 11 Enterprise (corporate-managed laptop)
  • Claude Desktop: 1.17377.1 (2b3ab4)
  • Claude Code CLI (standalone, npm-global): 2.1.177, commit 6fae7a072b11
  • Network: corporate-managed, Zscaler root CA present on host trust store

Symptom

Cowork and Code tabs in Claude Desktop fail consistently with:
API Error: Unable to connect to API: SSL certificate has expired
Requests retry ~8-10 times over 2-3 minutes before failing. The Chat tab is unaffected and works normally throughout.

Onset was sudden — began overnight (June 30 → July 1, 2026) with no known local config change at that time.

Troubleshooting already ruled out (in order)

  1. Suspected a newly-added Obsidian MCP connector (added around the same time issues started). Removed it entirely, started a fresh Cowork session — SSL error persisted identically. Rules out MCP as the cause.
  2. Corporate cert store — attempted exporting/importing the corporate root CA (Zscaler) into Cert:\LocalMachine\Root per common corporate-proxy advice. No effect.
  3. Per community findings in #71727 (Bun 1.4.0 regression in 2.1.181–2.1.195 breaking OAuth/cert verification), downgraded standalone CLI from 2.1.162 → 2.1.177 via npm install -g @anthropic-ai/claude-code@2.1.177. Confirmed via claude -v and claude doctor — clean diagnostics, all Remote Control/OAuth checks green.
  4. Restarted Claude Desktop entirely — Cowork/Code session requests still fail identically on relaunch.
  5. **Opened a terminal inside the failing Code tab session itself and ran claude doctor there. It resolved to the same patched npm-global (2.1.177) binary with clean diagnostics — while, in the same window at the same time, the session's own request panel above showed "Request failed · retrying (8/10)"** and ultimately failed with the same SSL cert error.

Key finding

Step 5 is the important one: the Cowork/Code session's request pipeline is a separate code path from the local CLI binary it's sitting next to. The correctly-patched, verified-working claude.exe (2.1.177, Bun 1.3.14) is reachable and functional via manual invocation inside the very same Code tab terminal — but the session orchestration itself still fails. This suggests Desktop's Cowork/Code session engine uses its own internal runtime/cert bootstrap (possibly inside the sandboxed VM), independent of whatever CLI version is installed and resolvable on the host via PATH.

This decouples the issue from anything CLI-version or Bun-version fixable on the user side — the standalone downgrade workaround does not apply to the Desktop session engine.

Question for maintainers

Does Claude Desktop's Cowork/Code session runtime bundle its own separate engine/cert handling independent of the host's installed Claude Code CLI (as reported by claude -v)? If so, is there a Desktop-specific version or fix in progress, since CLI-side pinning doesn't touch it?

Happy to provide claude doctor output, screenshots, or further repro steps.

skohuspaycor · 1 month ago

Update for me for work-around that fixed it for me was removing an expired cert from my root stores then restarting claude desktop.

Root cause: the expired DST Root CA X3 cert (expired 2021, but still present in both Cert:\LocalMachine\Root and Cert:\CurrentUser\Root) was sitting alongside the valid ISRG Root X1. Whatever cert chain-building logic Desktop's Cowork/Code engine uses appears to have preferred building the path through the expired cross-sign rather than terminating at the valid self-signed root — a known, previously-documented Let's Encrypt chain-building pitfall.
Fix: removing that one expired cert from both root stores, then fully restarting Claude Desktop.

Powershell commands to tell if you have the same expired cert as me:
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object { $_.Subject -like "DST Root CA X3" } | Select-Object Subject, NotAfter, Thumbprint
Get-ChildItem -Path Cert:\CurrentUser\Root | Where-Object { $_.Subject -like "DST Root CA X3" } | Select-Object Subject, NotAfter, Thumbprint

(Mine was expired 9/30/2021)

Powershell command to remove it (need to run as administrator for machine store):
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object { $_.Subject -like "DST Root CA X3" } | Remove-Item
Get-ChildItem -Path Cert:\CurrentUser\Root | Where-Object { $_.Subject -like "DST Root CA X3" } | Remove-Item

But if system is a managed laptop, this might just be a temp fix as next policy sync could restore it.

<img width="844" height="466" alt="Image" src="https://github.com/user-attachments/assets/2d803763-88ff-49aa-a514-bfc6b6d443ba" />

Showing cached comments. Read the full discussion on GitHub ↗