claude update fails with "getaddrinfo EREFUSED" on split-DNS VPN — updater resolves via c-ares, not the system resolver
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?
On Linux with systemd-resolved and a split-tunnel VPN, claude update consistently fails to fetch the version manifest, even though downloads.claude.ai is fully reachable.
In the same Node runtime, fetch() and dns.lookup() resolve the host fine — only the updater fails. The reason: the updater resolves DNS via c-ares (dns.resolve*), which reads /etc/resolv.conf directly and bypasses NSS / /etc/hosts / systemd-resolved. EREFUSED is a c-ares-specific code (glibc getaddrinfo returns EAI_* codes like ENOTFOUND), which is the tell that this is the c-ares path, not the OS resolver.
My VPN pushes internal authoritative resolvers into /etc/resolv.conf that refuse recursion for public names, and lists them before the systemd-resolved stub (127.0.0.53). c-ares queries the first server, gets REFUSED, and stops — it never reaches the stub, which resolves correctly. So the updater resolves differently, and more fragilely, than the rest of the app.
What Should Happen?
The updater's manifest fetch should resolve downloads.claude.ai the same way the rest of Claude Code does — via the OS resolver (getaddrinfo / dns.lookup), honoring NSS, /etc/hosts, and systemd-resolved split-DNS. It already works for fetch() in the same process, so the updater should succeed in any environment where fetch/curl succeed.
If c-ares is intentional (e.g. to avoid the libuv threadpool), it should at least fall back to dns.lookup on EREFUSED/ESERVFAIL rather than treating the first server's REFUSED as terminal.
Error Messages/Logs
Error: Failed to install native update
Error: Failed to fetch version from https://downloads.claude.ai/claude-code-releases/latest after 3 attempt(s): getaddrinfo EREFUSED downloads.claude.ai
strace -f -e trace=connect on `claude update`, filtered to DNS endpoints:
11 connect(.., {AF_INET, sin_port=htons(53), sin_addr="10.x.x.53"}) = 0 <- internal resolver, refuses public names
1 connect(.., {AF_UNIX, sun_path="/run/systemd/resolve/io.systemd.Resolve"}) = 0
The 11 connects are c-ares retrying across the 3 update attempts. The internal server answers REFUSED; c-ares does not fall through to the 127.0.0.53 stub listed later in resolv.conf.
Steps to Reproduce
On a host where the first nameserver in /etc/resolv.conf returns REFUSED for public names (e.g. an internal authoritative DNS server pushed by a VPN), while systemd-resolved/the stub resolves correctly:
# c-ares path (what the updater uses) — FAILS
node -e "require('dns').resolve4('downloads.claude.ai',(e,r)=>console.log(e?e.code:r))" # -> EREFUSED
# getaddrinfo path (NSS -> systemd-resolved) — WORKS
node -e "require('dns').lookup('downloads.claude.ai',(e,r)=>console.log(e?e.code:r))" # -> 35.x.x.x
# global fetch to the exact updater URL — WORKS
node -e "fetch('https://downloads.claude.ai/claude-code-releases/latest').then(r=>console.log(r.status))" # -> 200
# c-ares forced at the systemd-resolved stub — WORKS (stub does split-DNS correctly)
node -e "const d=require('dns');d.setServers(['127.0.0.53']);d.resolve4('downloads.claude.ai',(e,r)=>console.log(e?e.code:r))" # -> 35.x.x.x
# the updater itself — FAILS
claude update # -> getaddrinfo EREFUSED downloads.claude.ai
### Claude Model
None
### Is this a regression?
I don't know
### Last Working Version
N/A
### Claude Code Version
2.1.193
### Platform
Anthropic API
### Operating System
Ubuntu/Debian Linux
### Terminal/Shell
Xterm
### Additional Information
[claude-update-cares-bugreport.md](https://github.com/user-attachments/files/29397285/claude-update-cares-bugreport.md)
**Root cause:** the native updater's version-manifest fetch uses c-ares (`dns.resolve*`, or an HTTP client wired with a c-ares `lookup`) instead of the system resolver. c-ares reads `/etc/resolv.conf` directly, ignores NSS/`systemd-resolved`, queries servers in file order, and treats the first `REFUSED` as terminal. Because the VPN writes a public-refusing authoritative server as the first nameserver, c-ares fails where `getaddrinfo` (and `fetch`) succeed.
**Environment:** Ubuntu-family Linux, `systemd-resolved` active (NSS `nss-resolve` enabled), split-tunnel VPN (Cisco Secure Client, `cscotun0`), Node v24, shell `bash`. `/etc/resolv.conf` lists internal resolvers before `127.0.0.53`.
**Workarounds that work for me:**
- Update via the signed Linux package repo (`apt`/`dnf`/`apk` — these resolve through `getaddrinfo`).
- Or the curl installer: `curl -fsSL https://claude.ai/install.sh | bash` (curl uses `getaddrinfo`).
- `"DISABLE_AUTOUPDATER": "1"` in `settings.json` silences the failing background checks (doesn't fix resolution).
_Note: the "suggested fix" is inferred from observed behavior, not from the source._This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗