Skip to content
C.W.K.
Lesson 03 of 05 · published

The Gate That Reads the Binary

~14 min · macos, local-network-privacy, launchd, python, ehostunreach

Level 0Loose File
0 XP0/43 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

A Connect That Works in One Window and Not the Other

Here is a failure that looks like a network problem and is not one. Take a Python 3.12 from a Conda environment and open a TCP connection to another Mac's address on the home LAN. From your Terminal it may work. Start the very same interpreter, with the very same script, from a launchd agent, and the connect fails at once with EHOSTUNREACH: "No route to host". Ping works. The other Mac is up. The route table is fine. Nothing about the network changed between the two attempts.

What changed is who asked. Recent macOS releases put access to the local network behind a privacy permission, the same family of consent as the camera or the microphone. The system decides per process identity whether that process may reach devices on the local network, and when it has not decided yet, it asks the user with a prompt. A launchd agent has no window, so it cannot answer that prompt. Its connect is simply refused, and the refusal surfaces as the most misleading errno available.

What the Gate Covers and What It Does Not

Delta's builders measured the edges of this gate instead of guessing them, and the edges are what make a design possible:

DestinationBehind the gate?
Another Mac's LAN or Thunderbolt-bridge addressYes
Loopback (127.0.0.1)No
A tailnet peer through the utun interfaceNo
The public internetNo

And the gate looks at the binary. Apple-signed platform binaries, like /usr/bin/ssh, /usr/bin/rsync and /usr/bin/python3, are let through. A Conda python3.12 is a third-party binary and is not. On the workbench, measured on the day Delta was born, the same TCP connect to the service host's Thunderbolt and LAN addresses succeeded from Apple's python3 and answered EHOSTUNREACH from a Conda python3.12 started from a Claude Code shell. Under launchd the verdicts even differed from Mac to Mac, by whatever had once been granted on each.

Two more properties make this gate nasty to debug. The verdict is fixed when the process starts, so the failure reproduces exactly, every time, which makes it look like a deterministic network fault. And a service's health check usually passes (it is answered on loopback), while its outbound reads fail with a client library's generic "all connection attempts failed". From the outside it reads like a data problem.

The Family Rule: Route Around It

The tempting fix is in System Settings: grant the permission and move on. The family's rule is the opposite, and the reason is practical. A grant is per Mac, per binary identity, invisible in any repository, and lost the next time an environment is rebuilt. Nobody can review it, and nothing re-applies it. So the rule is to route around the gate, never to fix it in System Settings.

Delta is that route, in two moves:

  1. Delta runs on Apple's /usr/bin/python3. It is the one process on each Mac that dials the LAN, and as a platform binary it is allowed to. Its LaunchAgent's ProgramArguments start with /usr/bin/python3, not a Conda path. This is the one deliberate exception to the family's "every service runs in its own named Conda environment" rule, and the repository says so in a section of its own.
  2. Apps never dial the LAN. An app on a Mac, running on whatever Python it likes, asks its own Mac's Delta on loopback, which the gate never covers. Delta makes the LAN call and hands back the bytes. That is invariant 2: only Delta dials the LAN.

The second move is the one that scales. You do not have to audit forty apps' interpreters for gate standing. Exactly one binary per Mac needs it, and that binary is part of the operating system.

Diagnosing It in One Step

When you suspect the gate, do not stare at routes and firewalls. Run the same binary with the same script twice: once from your shell, once as a throwaway launchd job with launchctl submit. If the shell connects and the launchd job gets EHOSTUNREACH, you have your answer. Then run the job again with /usr/bin/python3 to see the platform binary pass. The second code block below does exactly that.

Code

probe.py: one TCP connect, the errno named·python
# probe.py: one TCP connect to host:port, reported with the errno's name.
# Usage: python3 probe.py 192.0.2.10 5201
import errno
import socket
import sys

host, port = sys.argv[1], int(sys.argv[2])
try:
    with socket.create_connection((host, port), timeout=3):
        print('connected', host, port, 'from', sys.executable)
except OSError as exc:
    name = errno.errorcode.get(exc.errno, str(exc.errno))
    print('failed', host, port, name, exc.strerror, 'from', sys.executable)
The same probe from a shell and from launchd, two interpreters·bash
# Compare one connect four ways: shell vs launchd, Conda python vs Apple's.
# PEER is another Mac's LAN address (a documentation address shown here).
PEER=192.0.2.10
PORT=5201
CONDA_PY="$HOME/miniconda3/bin/python3"
OUT="$HOME/probe-out"
mkdir -p "$OUT"

"$CONDA_PY" probe.py "$PEER" "$PORT"
/usr/bin/python3 probe.py "$PEER" "$PORT"

for py in "$CONDA_PY" /usr/bin/python3; do
  label="probe.$(basename "$(dirname "$(dirname "$py")")")"
  launchctl submit -l "$label" -o "$OUT/$label.out" -e "$OUT/$label.err" \
    -- "$py" "$PWD/probe.py" "$PEER" "$PORT"
  sleep 4
  launchctl remove "$label"
  cat "$OUT/$label.out" "$OUT/$label.err"
done

External links

Exercise

With two Macs on the same LAN, run the probe four ways: a third-party Python (Conda, Homebrew or python.org) from your shell, the same from a launchctl submit job, and /usr/bin/python3 both ways. Record which combinations connect. Then repeat with the other Mac's tailnet address instead of its LAN address and note what changes.
Hint
Point the probe at a port that is actually listening on the other Mac (for example python3 -m http.server 8000 there), or a refused connect (ECONNREFUSED) will hide the result you are after. ECONNREFUSED means you reached the Mac; EHOSTUNREACH from the third-party Python while /usr/bin/python3 connects means the gate stopped you. It can hit that Python from the shell, from launchd, or both, depending on what was once granted on that Mac; Apple's python3 should connect both ways.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.