[BUG] Claude Code CLI/Bash tool cannot access LAN devices on macOS — missing Info.plist/NSLocalNetworkUsageDescription, recurring across 5+ closed/locked issues, never fixed

Status Open
Reported on v2.1.199
Maintainer reply None cached
Activity 4 comments · opened Jul 5, 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?

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

  1. On macOS, use Claude Code (VS Code extension) with the Bash tool
  2. Run ping <LAN IP> or ssh user@<LAN IP> for any device on the local subnet
  3. Observe: ping: sendto: No route to host, immediate failure (not a timeout)
  4. 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_

View original on GitHub ↗

4 Comments

Gouldian · 22 days ago

Data point: NSLocalNetworkUsageDescription now 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:

$ defaults read "/Applications/Claude.app/Contents/Info.plist" NSLocalNetworkUsageDescription
Claude needs access to your local network to reach development servers and other local devices.

$ defaults read "~/Library/Application Support/Claude/claude-code/2.1.222/claude.app/Contents/Info.plist" NSLocalNetworkUsageDescription
Claude Code connects to servers and devices on your local network when commands you run need to reach them.

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:

$ curl -v --connect-timeout 3 telnet://192.168.1.82:22
* Immediate connect fail for 192.168.1.82: No route to host
* Failed to connect to 192.168.1.82 port 22 after 1 ms: Couldn't connect to server

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

  • Safari on the same Mac loads a LAN web UI at http://192.168.1.10/ with no trouble.
  • mDNSResponder resolves *.local names over the same interface live (dns-sd -G v4 returns a fresh answer with Flags 2, not a cached 40000002).
  • Another host on the same LAN pings this Mac at 0.28 ms, so inbound is unaffected.

Only the Claude Code process tree is blocked. The responsible bundle is the nested one:

/bin/zsh
 └ ~/Library/Application Support/Claude/claude-code/2.1.222/claude.app/Contents/MacOS/claude
      Identifier = com.anthropic.claude-code
    └ /Applications/Claude.app/Contents/Helpers/disclaimer
       └ /Applications/Claude.app/Contents/MacOS/Claude
            Identifier = com.anthropic.claudefordesktop

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-code is 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 tccutil failures are consistent with #80072, which locates the deny record in /Library/Preferences/com.apple.networkextension.plist rather than in the TCC database — which would explain why tccutil cannot 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_NETWORK is not set anywhere in this process environment (checked in the shell, and in the environments of both the claude and Claude processes). 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.

HunterWare · 22 days ago

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.

dwburks · 18 days ago

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 / NSLocalNetworkUsageDescription fix landed — and the bug survived it

The earlier threads (#27828, #54313) root-caused this as "the CLI is a bare Mach-O with no bundle and no Info.plist, so bundle_id is (null)". That is no longer true. On 2.1.227:

$ codesign -dv --verbose=2 .../claude-code/2.1.227/claude.app
Identifier=com.anthropic.claude-code
Format=app bundle with Mach-O thin (arm64)
Info.plist entries=12
TeamIdentifier=Q6L2SF6YDW
$ plutil -p .../claude-code/2.1.227/claude.app/Contents/Info.plist
  "CFBundleIdentifier" => "com.anthropic.claude-code"
  "CFBundleName" => "Claude Code"
  "NSLocalNetworkUsageDescription" => "Claude Code connects to servers and devices
      on your local network when commands you run need to reach them."
  ...

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.client entitlement 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.plist shows the Local Network grant store holds (bundle id, absolute executable path) pairs. For this identity it currently contains 17 rows across 16 distinct versions:

com.anthropic.claude-code → .../claude-code/2.1.34/claude
com.anthropic.claude-code → .../claude-code/2.1.41/claude
com.anthropic.claude-code → .../claude-code/2.1.51/claude
com.anthropic.claude-code → .../claude-code/2.1.63/claude
com.anthropic.claude-code → .../claude-code/2.1.78/claude.app/Contents/MacOS/claude
com.anthropic.claude-code → .../claude-code/2.1.92/claude.app/Contents/MacOS/claude
com.anthropic.claude-code → .../claude-code/2.1.111/claude.app/Contents/MacOS/claude
   ... 2.1.119, 2.1.121, 2.1.128, 2.1.138, 2.1.149, 2.1.197, 2.1.205, 2.1.209 ...
com.anthropic.claude-code → .../claude-code/2.1.217/claude.app/Contents/MacOS/claude
com.anthropic.claudefordesktop → /Applications/Claude.app/Contents/MacOS/Claude

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.217

Occurrences 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:

  • Enabling every visible claude.app row (there are dozens — all the orphans), plus the Claude.app row, plus Claude Code URL Handler.app: no effect. They're all stale paths.
  • Restarting the app repeatedly, confirming the PID changed each time: no effect.
  • tccutil reset LocalNetwork com.anthropic.claude-codeFailed to reset LocalNetwork approval status, with and without sudo. Local Network approvals are not in the TCC database, so tccutil has no such service and there is no supported reset path.
  • Reinstalling would not help — the grant keys on a path the next update invalidates anyway.

Worth noting the rows render as claude.app (the directory basename) rather than Claude Code (the CFBundleName), 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 log is a zsh builtin, so the full path is required):

/usr/bin/log show --last 60s --style compact \
  --predicate 'eventMessage CONTAINS[c] "local network"'

[com.apple.networkextension:] LocalNetwork: found bundle id com.anthropic.claude-code by PID

The grant store:

plutil -p /Library/Preferences/com.apple.networkextension.plist | grep -A1 claude-code

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:

ping -c1 <your-default-gateway-ip>   # LOCAL subnet  → "ping: sendto: No route to host"
ping -c1 8.8.8.8                     # REMOTE, routed THROUGH that same gateway → replies fine

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 rules is clean, and the identical ssh command 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:

  1. Launch the CLI from a stable path. Keep versioned directories for staging, but have the app exec a fixed path — e.g. .../claude-code/current/claude.app as a symlink or bind, or a small stable launcher bundle. One grant then survives every update. This is the single change that fixes it.
  2. Have the updater clean up after itself. Revoke/migrate the prior version's rows instead of orphaning them. This is the other half of #58279, which was closed as not planned; the two together are why users see dozens of rows and can't tell which to trust.
  3. Failing both, detect and surface it. When a LAN connection returns 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 bare No route to host that 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:

ssh -N -L 2222:localhost:22 user@lan-host

then connect to localhost:2222 from inside Claude Code. Unlike "just use a terminal", this keeps your desktop session and any running background work intact.

scandora · 14 days ago

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/ssh leave room for ICMP- or SSH-specific explanations. This is a ~30-line C program that does nothing but a single POSIX connect():

int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in sa = { .sin_family = AF_INET, .sin_port = htons(port) };
inet_pton(AF_INET, argv[1], &sa.sin_addr);
int rc = connect(fd, (struct sockaddr *)&sa, sizeof sa);

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 --strict clean, flags=0x0(none), Authority= present | Blocked |
| Same binary in a signed .app with NSLocalNetworkUsageDescription, 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 my NSLocalNetworkUsageDescription string 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 .app wrapping socat as a loopback→LAN forwarder, approved once via open -a, works headlessly indefinitely:

127.0.0.1:<local-port>  →  <LAN host>:<port>

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.app on disk and a dozen-plus claude.app rows in the Local Network pane, plus 3× Google Chrome.app for one installed Chrome. Anything keyed to a per-version install path will keep minting entries.

Happy to supply the full probe source or codesign output if useful.