"The service is healthy, its health check is green, and every call it makes to the other machine fails. Same binary, same address, works instantly from your shell."
A Third Time, the Same Shape
You've now seen this twice. A synced path that resolves instantly in your shell and hangs under the service manager. A permission the operating system grants your interactive session and denies the same interpreter when the service manager launches it. Here is the third, and it is worth a lesson of its own because it is the one that looks least like an infrastructure problem.
Recall's control plane dials the other machine for some of its derived work. One day those calls started failing — every one of them, immediately, with "no route to host." The obvious conclusion is that the other machine is down. It isn't. Its health check answers. You SSH over and run the exact same call, from the exact same Python, against the exact same address, and it returns in a fraction of a second. Re-run the service and it fails again, identically, every time.
That determinism is the tell, and it's the opposite of what your instincts say. A network that is genuinely flaky fails intermittently. A verdict that is fixed — the same answer every single run, from one launch context and not the other — is not the network. It's a policy decision being made about your process before the packet ever leaves.
What Modern macOS Actually Gates
Recent macOS treats talking to other machines on your own local network as a privacy-relevant capability, the same way it treats the camera. Apple's technote spells out the rules, and two of them do all the damage here:
- Who is auto-allowed. Daemons started by the service manager are allowed. Programs running as root are allowed. Command-line tools you run from Terminal or over SSH are allowed, and so is anything they spawn. That last clause is exactly why your by-hand test passes.
- The exception you were counting on doesn't cover you. The auto-allow for launch daemons explicitly does not extend to launch agents — and a per-user background service, the ordinary way to run something that belongs to a logged-in account, is an agent. The category that fails is the category almost everyone actually ships.
And the grant doesn't attach to your code. The system tracks down the responsible program and records the user's choice against that — the identical model as the file-access grant in the previous lesson, in a different subsystem. A bundled app with a stable identity gets one durable row that a person can recognize and approve. A bare interpreter does not: every environment you build mints another anonymous entry, and rebuilding one mints a fresh one that has to be approved all over again. You cannot manage a list you cannot read.
The Fix Is to Stop Being on the Local Network
Here is where this war story diverges from the last one. With file access, the answer was to own a stable identity — a signed launcher the grant could live on. That worked because there was exactly one identity to own. Here there isn't: the list grows without bound and every row looks the same, so no amount of clicking ever converges.
So the fix is to leave the gated network entirely. The technote's own definition is the escape hatch: a local network is one associated with a broadcast-capable interface — Wi-Fi and Ethernet — and it explicitly excludes VPN interfaces. Traffic over an encrypted overlay network between your own machines is therefore not a local network operation at all, and never meets the gate. Recall's inter-machine calls now address peers by their overlay names rather than by a name that ends in .local or an address on the wire in the next room. The same call, over a different route, stops being the kind of call that needs permission.
The same definition explains the symptoms that seemed unrelated. Resolving a name ending in .local requires local network access on its own, so mDNS names fail before a connection is even attempted. A direct point-to-point link between two machines in the same room is a broadcast-capable interface like any other, so it is gated exactly as hard as the house network — which feels absurd until you remember the gate is about who is being talked to, not how far away they are.
The Diagnostic, and Why You Need One
This failure is exceptionally good at sending you to the wrong machine. The remote service stays healthy, so the dashboard is green. Only the code paths that dial out break, so the symptom looks like missing data rather than a network fault. The client library reports it as a connection error, which reads as "the server is down." Every instinct points away from the truth.
So learn the one-step test, and reach for it whenever a background service and your shell disagree: run the same binary under the service manager and from a shell, and compare. Two different answers from one binary against one address is not a network condition. It is a launch-context verdict, and this whole track has been three variations on that single sentence.