[BUG] Claude Code CLI/Bash tool cannot access LAN devices on macOS — missing Info.plist/NSLocalNetworkUsageDescription, recurring across 5+ closed/locked issues, never fixed
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?
This is a recurring, already well-diagnosed issue — please do not auto-close as stale again
This exact bug has already been independently reported and root-caused multiple times, and each report gets auto-closed by the stale-bot after ~30 days of inactivity without an actual fix ever shipping:
#27828 — closed as "not planned" (locked)
#54313 — closed as duplicate of #27828 (locked)
#47040 — closed as "not planned" (locked)
Also referenced in that history: #34402, #36675, #37326, #29541, #56433
Multiple independent users, on different Macs/macOS versions, over several months, have traced this to the exact same root cause. Filing again because all prior threads are now locked and can no longer be commented on, and the underlying bug is still present as of Claude Code CLI/extension version 2.1.199 on macOS 26.5.1.
Summary
The Bash tool (both the VS Code extension and, per other reports, the Desktop/CLI wrapper) cannot reach any device on the user's local LAN (e.g. 192.168.1.x) — ping/ssh/any socket connection to a LAN IP fails immediately with sendto: No route to host / EHOSTUNREACH. Only the default gateway and internet destinations are reachable. Confirmed not a network/router/account issue: the identical command from the same Mac, same user, in Terminal.app or via Safari works perfectly and instantly.
Root cause (confirmed independently by multiple reporters, including this one)
The native claude binary is a bare Mach-O executable, not packaged inside a proper .app bundle (Info.plist=not bound per codesign -dvvv). macOS's Local Network privacy system (TCC) requires a valid bundle identifier + an NSLocalNetworkUsageDescription key in Info.plist to:
show the permission prompt to the user at all, and
register a manageable entry in System Settings → Privacy & Security → Local Network.
Without it, the permission check silently fails closed (bundle_id: (null) / allowed: 0 in nehelper/UserEventAgent logs — see log evidence in #27828 and my own reproduction below), with no UI path for the user to ever grant access. This is independent of the OS-level network config, router, firewall, VPN, or user account — verified exhaustively in this report and in the prior linked issues.
One prior investigator (#54313) also found that even after manually patching in NSLocalNetworkUsageDescription and re-signing, the prompt still doesn't fire, because Apple's Local Network prompt is only triggered by Network.framework (nw_connection_*) calls — not by the POSIX connect() calls used by most CLI tools (curl, node, ssh, etc.). So the real fix needs both:
Ship NSLocalNetworkUsageDescription (and ideally com.apple.security.network.client) in a proper .app-bundled Info.plist for the claude binary, and
Have the binary make one explicit Network.framework call (e.g. nw_path_monitor or nw_connection_start to any LAN-scoped target) early at startup, so macOS actually surfaces the permission prompt and registers the bundle in the Local Network list.
My own reproduction (independent confirmation, 2026-07-05)
Environment: Claude Code VS Code extension 2.1.199, macOS 26.5.1 (Build 25F80), Mac mini M4
Process tree traced: zsh → .../anthropic.claude-code-2.1.199-darwin-arm64/resources/native-binary/claude → Code Helper (Plugin) → VS Code
codesign -dvvv on the claude binary: Identifier=com.anthropic.claude-code, Authority=Developer ID Application: Anthropic PBC (Q6L2SF6YDW), hardened runtime, Info.plist=not bound
Entitlements: no App Sandbox, no network entitlements (consistent with non-sandboxed process)
log stream at the moment of a failing ping showed the Local Network subsystem actively inspecting the process
Ruled out first: router AP/client isolation, target/router firewalls, macOS Application Firewall (disabled), pf (globally disabled), VPN/MDM/Screen Time (none present), Docker Desktop daemons, both Wi-Fi and wired Ethernet (identical failure on each), full macOS network-preferences reset (x2 + reboot), toggling the visible "Visual Studio Code" Local Network entries, tccutil reset All com.microsoft.VSCode, and a full VS Code uninstall + fresh reinstall — none of it changed the outcome, consistent with the bundle-identity root cause above (there was never a real permission entry for this specific unbundled binary to begin with).
Confirmed working from the exact same Mac/user/network via Terminal.app (SSH + ping, 40+ packets, sub-2ms) and via Safari (real HTTP responses from the target's nginx server) — proving the network, router, and Mac are not at fault.
Impact
Affects any Claude Code user on macOS trying to use the Bash tool to reach LAN-only infrastructure: home servers, Raspberry Pis, internal dev/staging boxes, NAS, MCP servers on private IPs, etc. The failure mode (No route to host) is indistinguishable from a real network/firewall problem, so affected users burn significant time debugging their own network before finding this is a Claude Code packaging bug — as documented across all the linked issues above.
Ask
Please reopen/unlock or link this to an actively tracked internal ticket rather than letting the stale-bot close it again — this has now been independently reported and root-caused at least 5+ times over several months with no fix shipped. Suggested fix is described above (bundle + NSLocalNetworkUsageDescription + an early Network.framework call to trigger the permission prompt).
What Should Happen?
Claude Code's Bash tool should be able to reach LAN devices (ping/ssh) just like any other process on the same Mac/user account already can (confirmed working via Terminal.app and Safari).
Error Messages/Logs
ping: sendto: No route to host
PING 192.168.1.205 (192.168.1.205): 56 data bytes
Request timeout for icmp_seq 0
--- 192.168.1.205 ping statistics ---
3 packets transmitted, 0 packets received, 100.0% packet loss
ssh: connect to host 192.168.1.205 port 22: No route to host
UserEventAgent: [com.apple.networkextension:] LocalNetwork: found bundle id com.microsoft.VSCode by PID
UserEventAgent: [com.apple.networkextension:] LocalNetwork: found bundle id com.microsoft.VSCode by UUID <...>
Steps to Reproduce
- On macOS, use Claude Code (VS Code extension) with the Bash tool
- Run
ping <LAN IP>orssh user@<LAN IP>for any device on the local subnet - Observe:
ping: sendto: No route to host, immediate failure (not a timeout) - From the same Mac, same user account, run the identical command in Terminal.app → works perfectly
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.199
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
VS Code integrated terminal
Additional Information
_No response_
4 Comments
Data point:
NSLocalNetworkUsageDescriptionnow ships in both bundles, and the bug is unchanged.Environment: macOS 27.0 (build 26A5388g), Claude Code 2.1.222 running inside the Claude desktop app.
The root cause stated in this issue no longer holds as of 2.1.222 — the key is present in both Info.plists:
The key is there. The behaviour is identical: no prompt is ever shown, no entry is ever created under System Settings › Privacy & Security › Local Network, and every connection to the local subnet fails. So adding the usage description was not sufficient, and this issue should not be closed on the assumption that it was.
Symptom shape
Any socket to the local subnet fails before egress, in about 1 ms:
Public destinations from the same process are fine —
https://1.1.1.1/returns 301 in 4.6 ms. The routing table, ARP cache and link state are all healthy, and the NIC reports 0 input and 0 output errors across ~49M/40M packets.It is scoped to this app, not to the machine
http://192.168.1.10/with no trouble.mDNSResponderresolves*.localnames over the same interface live (dns-sd -G v4returns a fresh answer with Flags 2, not a cached 40000002).Only the Claude Code process tree is blocked. The responsible bundle is the nested one:
Both are validly signed (
Developer ID Application: Anthropic PBC (Q6L2SF6YDW)).Everything available to a user, and none of it works
| Attempt | Result |
|---|---|
| System Settings › Privacy & Security › Local Network |
com.anthropic.claude-codeis not listed at all — there is nothing to toggle. Every app that is listed (dozens of per-version Chrome/Edge entries, plus Python.app, Roon, zoom.us, LibreOffice…) is enabled. || Quit and relaunch the desktop app | No change, no entry created |
| Force the request from the app's own in-app browser, in the foreground | Navigation denied, still no prompt |
|
tccutil reset LocalNetwork com.anthropic.claudefordesktop|tccutil: Failed to reset LocalNetwork approval status||
tccutil reset LocalNetwork com.anthropic.claude-code|tccutil: Failed to reset LocalNetwork approval status||
sudo tccutil reset All com.anthropic.claude-code| No change |The
tccutilfailures are consistent with #80072, which locates the deny record in/Library/Preferences/com.apple.networkextension.plistrather than in the TCC database — which would explain whytccutilcannot reach it. That file is root-only (-rw------- root wheel), so I was not able to verify the record contents on this machine; I am reporting that as unverified rather than assuming it.Not the same as #37994
OPERON_SANDBOXED_NETWORKis not set anywhere in this process environment (checked in the shell, and in the environments of both theclaudeandClaudeprocesses). So this is a separate path to the same symptom, and fixing the sandbox env var alone would not address it.Impact
Claude Code cannot reach anything on the LAN — no SSH to a local server, no HTTP to a local device, no
ping— while every other application on the machine can, and there is no supported way for the user to grant the permission. In my case the work was only possible by routing every LAN operation through an off-LAN host over a mesh VPN, and by using Safari for anything with a web UI.I'm astounded that the only work-around is to literally stop your gui session and resume in the tui. This is a gap in the app functionality that has been documented in multiple issues and never worked on. Please fix it if you want to maintain a solid Claude Code gui app.
Still reproducing on macOS 27.0 (build 26A5406e) with Claude Code 2.1.227 in the desktop app.
Two things to add, because I think the root cause has moved since the earlier reports and the fix they ask for has already shipped:
1. The
Info.plist/NSLocalNetworkUsageDescriptionfix landed — and the bug survived itThe earlier threads (#27828, #54313) root-caused this as "the CLI is a bare Mach-O with no bundle and no
Info.plist, sobundle_idis(null)". That is no longer true. On 2.1.227:Proper app bundle, bound
Info.plist, usage description present. Everything #54313 asked for. LAN access is still denied. So closing this as "fixed by adding the Info.plist" would be premature — please don't.(Note also that the missing
com.apple.security.network.cliententitlement mentioned in earlier reports is a red herring — that entitlement only has meaning under App Sandbox, which this binary does not use.)2. The actual mechanism: the grant store is keyed on absolute binary path, and the installer uses a versioned path
Decoding
/Library/Preferences/com.apple.networkextension.plistshows the Local Network grant store holds(bundle id, absolute executable path)pairs. For this identity it currently contains 17 rows across 16 distinct versions:Versions present in the store:
2.1.34 2.1.41 2.1.51 2.1.63 2.1.78 2.1.92 2.1.111 2.1.119 2.1.121 2.1.128 2.1.138 2.1.149 2.1.197 2.1.205 2.1.209 2.1.217Occurrences of the running version,
2.1.227: zero. Only one version directory exists on disk (2.1.227); the other sixteen rows are orphans whose binaries were deleted by the updater.As an aside, this store also dates the #54313 fix precisely: rows up to 2.1.63 are
<version>/claude(bare binary), and from 2.1.78 onward they are<version>/claude.app/Contents/MacOS/claude.Why this makes the bug unfixable from the user side
Because the grant is path-keyed and the installer writes to
~/Library/Application Support/Claude/claude-code/<version>/, every auto-update produces a binary macOS has never seen. The row for the running version does not exist — it is not set to "off", it is absent. There is therefore nothing in System Settings → Privacy & Security → Local Network to enable.This is why the usual advice fails, and I verified each of these:
claude.approw (there are dozens — all the orphans), plus theClaude.approw, plusClaude Code URL Handler.app: no effect. They're all stale paths.tccutil reset LocalNetwork com.anthropic.claude-code→Failed to reset LocalNetwork approval status, with and withoutsudo. Local Network approvals are not in the TCC database, sotccutilhas no such service and there is no supported reset path.Worth noting the rows render as
claude.app(the directory basename) rather thanClaude Code(theCFBundleName), which is consistent with #27322's "dialog shows version number instead of app name" — both are symptoms of per-version-path identity.Diagnostics for anyone triaging this
Attribution (note
logis a zsh builtin, so the full path is required):→
[com.apple.networkextension:] LocalNetwork: found bundle id com.anthropic.claude-code by PIDThe grant store:
One-command discriminator that separates this from an actual network/routing problem — this is the check that saves the most time, because every other symptom mimics a dead host or a firewall:
Packets provably transit the gateway to reach the internet while the gateway's own address is unreachable. Only destination-scoped local-network enforcement produces that. Supporting evidence that it is not the network:
arp -n <host>resolves with a fresh MAC,route -n get <host>is normal,sudo pfctl -s rulesis clean, and the identicalsshcommand succeeds from a normal terminal — because there, attribution rolls up to the terminal app's own (stable-path, granted) identity.Suggested fix
The bundle metadata is already correct, so the remaining fix is about path stability, not
Info.plist:.../claude-code/current/claude.appas a symlink or bind, or a small stable launcher bundle. One grant then survives every update. This is the single change that fixes it.not planned; the two together are why users see dozens of rows and can't tell which to trust.EHOSTUNREACH/errno 65, say so explicitly — "this is likely the macOS Local Network permission, and the current version has no entry" — rather than surfacing a bareNo route to hostthat sends people hunting routes, firewalls and VPNs for hours.Workaround for anyone stuck right now, which needs no permission because loopback is exempt from Local Network enforcement — run a forward from a normal terminal:
then connect to
localhost:2222from inside Claude Code. Unlike "just use a terminal", this keeps your desktop session and any running background work intact.Independent confirmation on macOS 26.5.2 (build 25F84), Apple Silicon, Claude Code 2.1.229 — commenting here rather than filing a sixth duplicate. Adding four things this thread doesn't yet have: a minimal reproduction, empirical elimination of code signing as the fix, direct evidence for the Network.framework claim from #54313, and a working headless workaround better than an SSH tunnel.
Minimal reproduction — one binary, two parents
ping/sshleave room for ICMP- or SSH-specific explanations. This is a ~30-line C program that does nothing but a single POSIXconnect():Built with
clang -arch arm64(ad-hoc linker-signed). Same file path, same target, same minute:| Parent process | Result |
|---|---|
| iTerm |
CONNECTED|| Claude Code Bash tool |
FAILED errno=65 (No route to host), 3/3 |The binary is not the variable — the parent is. This matches Apple DTS guidance that a tool run from a terminal is exempt because the terminal is the responsible code.
Code signing is not the fix (measured)
Worth ruling out explicitly, since "just sign it" is the common first instinct:
| Attempt | Result |
|---|---|
| Re-signed ad-hoc | Blocked |
| Signed with a real certificate,
codesign --verify --strictclean,flags=0x0(none),Authority=present | Blocked || Same binary in a signed
.appwithNSLocalNetworkUsageDescription, inner executable invoked by path | Blocked, and no row ever appears in System Settings |Direct support for the Network.framework point from #54313
That last row is the interesting one. The same signed bundle, launched via
open -a, did raise the consent prompt immediately — showing myNSLocalNetworkUsageDescriptionstring verbatim — and after approval the bundled binary worked headlessly from then on.So bundling + the usage description is necessary but not sufficient: with POSIX
connect()and a headless invocation, no prompt fires and no entry is ever registered. That's independent empirical support for the #54313 finding quoted in the issue body, and it means the suggested fix genuinely needs both halves — the bundle and an early Network.framework call — not just the Info.plist.Scope refinement: only directly-attached subnets are gated
Possibly useful for others diagnosing this. LNP only evaluates destinations on a subnet the Mac has a directly-attached interface for. On this machine a ZeroTier overlay
/24(the Mac holds an address on it) is gated, while a private subnet merely routed through a peer gateway is completely unaffected — an ad-hoc-signed binary reaches it fine from the Bash tool. A partial failure like this reads as flaky routing and sends people down the wrong path.A better workaround than the SSH tunnel
Loopback is exempt from LNP entirely. So instead of tunnelling per-connection, a small signed
.appwrappingsocatas a loopback→LAN forwarder, approved once viaopen -a, works headlessly indefinitely:Agent-spawned tools connect to
127.0.0.1:<local-port>and never touch the gated subnet. The key property is that the forwarder holds the identity and never changes, so the one-time approval survives updates to whatever is behind it — whereas a grant tied to a frequently-updated binary is cleared every time that binary changes.Related
The accumulation behaviour in #79053 shows up here too: enrolment is per code identity and old rows are never garbage-collected. This Mac has one
claude.appon disk and a dozen-plusclaude.approws in the Local Network pane, plus 3×Google Chrome.appfor one installed Chrome. Anything keyed to a per-version install path will keep minting entries.Happy to supply the full probe source or
codesignoutput if useful.